diff --git a/.sonarcloud.properties b/.sonarcloud.properties index 5b1b30fee..9f0c853a4 100644 --- a/.sonarcloud.properties +++ b/.sonarcloud.properties @@ -1 +1,2 @@ -sonar.exclusions=**/*.gen.go, **/*.sql.go, pkg/server/datastore/datastore_test.go +sonar.exclusions=**/*.gen.go, **/*.sql.go +sonar.cpd.exclusions=**/*_test.go diff --git a/Makefile b/Makefile index 5c59312b8..9ba288e90 100644 --- a/Makefile +++ b/Makefile @@ -205,11 +205,13 @@ help: ### Code generation #### .PHONY: generate-sqlc-server generete-spec +# Run sqlc to generate sql code generate-sqlc-server: install-sqlc run-sqlc-server run-sqlc-server: $(sqlc_bin) generate --file $(server_sqlc_config_file) +# Run oapi-codegen to generate api code generate-spec: go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v$(oapi_codegen_version) cd ./pkg/common/api; $(GOPATH)/bin/oapi-codegen -config schemas.cfg.yaml schemas.yaml diff --git a/pkg/common/api/helper.go b/pkg/common/api/helper.go index 3c5d118bb..d07c9c299 100644 --- a/pkg/common/api/helper.go +++ b/pkg/common/api/helper.go @@ -73,7 +73,7 @@ func RelationshipFromEntity(entity *entity.Relationship) *Relationship { UpdatedAt: entity.UpdatedAt, TrustDomainAId: entity.TrustDomainAID, TrustDomainBId: entity.TrustDomainBID, - TrustDomainBConsent: entity.TrustDomainBConsent, - TrustDomainAConsent: entity.TrustDomainAConsent, + TrustDomainAConsent: ConsentStatus(entity.TrustDomainAConsent), + TrustDomainBConsent: ConsentStatus(entity.TrustDomainBConsent), } } diff --git a/pkg/common/api/helper_test.go b/pkg/common/api/helper_test.go index c771f2b54..a2c34c0b8 100644 --- a/pkg/common/api/helper_test.go +++ b/pkg/common/api/helper_test.go @@ -106,8 +106,8 @@ func TestRelationshipFromEntity(t *testing.T) { UpdatedAt: time.Now(), TrustDomainAID: uuid.New(), TrustDomainBID: uuid.New(), - TrustDomainAConsent: true, - TrustDomainBConsent: false, + TrustDomainAConsent: entity.ConsentStatusPending, + TrustDomainBConsent: entity.ConsentStatusAccepted, } r := RelationshipFromEntity(&eRelationship) @@ -118,7 +118,7 @@ func TestRelationshipFromEntity(t *testing.T) { assert.Equal(t, eRelationship.UpdatedAt, r.UpdatedAt) assert.Equal(t, eRelationship.TrustDomainAID, r.TrustDomainAId) assert.Equal(t, eRelationship.TrustDomainBID, r.TrustDomainBId) - assert.Equal(t, eRelationship.TrustDomainAConsent, r.TrustDomainAConsent) - assert.Equal(t, eRelationship.TrustDomainBConsent, r.TrustDomainBConsent) + assert.Equal(t, string(eRelationship.TrustDomainAConsent), string(r.TrustDomainAConsent)) + assert.Equal(t, string(eRelationship.TrustDomainBConsent), string(r.TrustDomainBConsent)) }) } diff --git a/pkg/common/api/schemas.gen.go b/pkg/common/api/schemas.gen.go index a8166348a..804389f43 100644 --- a/pkg/common/api/schemas.gen.go +++ b/pkg/common/api/schemas.gen.go @@ -17,18 +17,28 @@ import ( "github.com/getkin/kin-openapi/openapi3" ) +// Defines values for ConsentStatus. +const ( + Accepted ConsentStatus = "accepted" + Denied ConsentStatus = "denied" + Pending ConsentStatus = "pending" +) + // ApiError defines model for ApiError. type ApiError struct { Code int64 `json:"code"` Message string `json:"message"` } -// BundleDigest defines model for BundleDigest. +// BundleDigest base64 encoded SHA-256 digest of the bundle type BundleDigest = string // Certificate X.509 certificate in PEM format type Certificate = string +// ConsentStatus defines model for ConsentStatus. +type ConsentStatus string + // JWT defines model for JWT. type JWT = string @@ -37,13 +47,13 @@ type JoinToken = UUID // Relationship defines model for Relationship. type Relationship struct { - CreatedAt time.Time `json:"created_at"` - Id UUID `json:"id"` - TrustDomainAConsent bool `json:"trust_domain_a_consent"` - TrustDomainAId UUID `json:"trust_domain_a_id"` - TrustDomainBConsent bool `json:"trust_domain_b_consent"` - TrustDomainBId UUID `json:"trust_domain_b_id"` - UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` + Id UUID `json:"id"` + TrustDomainAConsent ConsentStatus `json:"trust_domain_a_consent"` + TrustDomainAId UUID `json:"trust_domain_a_id"` + TrustDomainBConsent ConsentStatus `json:"trust_domain_b_consent"` + TrustDomainBId UUID `json:"trust_domain_b_id"` + UpdatedAt time.Time `json:"updated_at"` } // SPIFFEID defines model for SPIFFEID. @@ -77,61 +87,62 @@ type UUID = openapi_types.UUID // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/common/api/schemas.yaml b/pkg/common/api/schemas.yaml index 8bdd019c0..1213a84ce 100644 --- a/pkg/common/api/schemas.yaml +++ b/pkg/common/api/schemas.yaml @@ -24,6 +24,7 @@ components: example: "trust.domain.com" BundleDigest: type: string + description: base64 encoded SHA-256 digest of the bundle example: f0456d7aed088e791e4610c3c2ad63afe46e2e777988fdbc9270f15ec9711b42 TrustBundle: $ref: '#/components/schemas/Certificate' @@ -79,11 +80,11 @@ components: trust_domain_b_id: $ref: '#/components/schemas/UUID' trust_domain_a_consent: - type: boolean - default: false + $ref: '#/components/schemas/ConsentStatus' + default: pending trust_domain_b_consent: - type: boolean - default: false + $ref: '#/components/schemas/ConsentStatus' + default: pending created_at: type: string format: date-time @@ -94,6 +95,12 @@ components: format: date-time maxLength: 21 example: "2021-01-30T08:30:00Z" + ConsentStatus: + type: string + enum: + - accepted + - denied + - pending JoinToken: $ref: '#/components/schemas/UUID' SPIFFEID: diff --git a/pkg/common/entity/entities.go b/pkg/common/entity/entities.go index 60e1d220b..3580f1ee2 100644 --- a/pkg/common/entity/entities.go +++ b/pkg/common/entity/entities.go @@ -7,7 +7,13 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" ) -// TODO: these entities will be defined by the OpenAPI specs and autogenerated. +type ConsentStatus string + +const ( + ConsentStatusAccepted ConsentStatus = "accepted" + ConsentStatusDenied ConsentStatus = "denied" + ConsentStatusPending ConsentStatus = "pending" +) type TrustDomain struct { ID uuid.NullUUID @@ -25,8 +31,8 @@ type Relationship struct { TrustDomainBID uuid.UUID TrustDomainAName spiffeid.TrustDomain TrustDomainBName spiffeid.TrustDomain - TrustDomainAConsent bool - TrustDomainBConsent bool + TrustDomainAConsent ConsentStatus + TrustDomainBConsent ConsentStatus CreatedAt time.Time UpdatedAt time.Time } diff --git a/pkg/server/api/admin/admin.gen.go b/pkg/server/api/admin/admin.gen.go index cb1edb6fb..c748c6d57 100644 --- a/pkg/server/api/admin/admin.gen.go +++ b/pkg/server/api/admin/admin.gen.go @@ -1312,30 +1312,30 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", - "z1iIFG+0v87aQCXY+ST/LwAA///i709UiRwAAA==", + "sZwmWdtPlsWXO97zPMc73eKAJylnwJTE/i0WIFPOJJg/RxCRLFb6MeBMATOPJE1jGhBFOXM/Sc70OxnM", + "ISH66YWACPv4F3e9r1uMSneU0mMhuMB5nls4BBkImup9sI/NABqdjtHaBT2rXKu3Xi3XToQh1StJfCp4", + "CkJR7XJEYgkWTiuvtOsh6N+Ii4Qo7GPK1HAPWzghX2mSJdjfPzy0cEJZ8a/neRZWNykUU2EGAucWTkBK", + "MjM7wVeSpLEeH6EpkEzRKIsRmBMsp1lre1IJymaFwffAZmqO/X7FSDmuTyvgS0YFhNi/LPxe252s5vPp", + "JwiU9ukPTtkF/wys7tUgIgf70XDP3n/Ve2Xv7Q/79nQQBXY/OBwOouGQRGRYdTDLaFh3bzC0cEqUAqGx", + "+efSsw+JHU1uD3J79bzX4bnXz1/gjYNWHD8DWTJsB0TV8sj3kW0dm2Zgi+Vt8TyD2PBazmm6K8sEEAXh", + "FVF1LPpev2d7PXvgXXgH/sDzPe9jNfYhUWArmkCDH72WqNFw26E/fBgf6ZlKZFJdhTwhlF2Rq0DrqVDv", + "favfFNPOFVGZbNnmgfanj2N/uoP9LA2fGI0Gp4yAKhyoudAWyLbD3YnbnQHdRuIz+JKB3FlfjwR714VN", + "fXYKVtvRL/SsIzPpO5Rv7cKrGjFuo+JwSM2JQgJSARphpOaAgCmqbtDfnS6VStr+Fb28HNkfTTL+d4Je", + "/vayNRnPibgGqUBcyZRGEXTA7fx0/PbtcQF6d3owksC2uRUIT/T03MKcTTkRIWWzq2nGwrjbHq+Lqf9b", + "MjCHvTsnbKHvSRmqtbdGAU5BEifgyXYy7B20gF2xcZrtmhieh8EbPj+INw1MzB5tQTfk/KHKJ300yiK+", + "rMtJYHAsooTfUTXPpppsIsY+niuVSt91Z+a1Jo77OyxiUOqUBJ+JCN0ZiUkoKMQbOQq/Ww6hcxDXINCf", + "hJEZJBpWXarLFAIalc2Agy0c0wCYhIo7o5QEc0B9x6u55LvuYrFwiBl1uJi55VLpvh+/OT45P7b7jufM", + "VWLcUlQZaDYcGoUJZcYXG/2VAtNPA2PrGoQsTtFzPKfXM5kkBUZSqjF2PGeADUpzQ21XVK5N82YGJqya", + "/2ZgHGoHQJ3VJuotBElAgZDYv2xoBFd3RdIUNigSPEEEFZopKItSEDqYil6DhTW82MdfMhA3y0yi4Tdl", + "kVVptoDphuVSt2SCX4NmYQiMmocUmE6ZFc6vs1XTy+q92W5cNeRldWz5NmU5sepNZt/zdmowqYJEbjNb", + "K+TzVQSIEOSmrfs8z4IApNRt3ArvQg6rDrjN3Oog7rJVNi1rliRE3BRsQaJBF0Vmmii4TqNJbuE0ayHc", + "abZBOFEUda95ePNorXlb0ZjXc6gSGeTfCF53zJ4NozfmgkakBhQqY4ymoBYADKkFrwn2PiRzq5FN3Nvq", + "3/FR3jm9nNXWbUs24yPEI3Pf1mJZKlqnurWgRXPnOtBd9V0W8JOfjxdau2R1w9XYsQV7kyvtcNWG3KXr", + "etZ9ClU3ir28VHQNqN5TWGvDqRBa+AjQjMKwCo2qFJ8VaKpybUHGvW3cafeqsnK2i42r8F5R1q75silo", + "0ePmBfswQT7+hfsNkD+PNLvhb3XR4feNbTXZPmmyaM8UPzptPpju+ykyh/uJU2avPkvflUTWn6R34dXJ", + "T5Yzmt/+nztvaKyQwQpFXFTpUMN9TQftMCpwmxhnpek9C+DqXXbMAxLPuVSOXJDZDIRDuUtS6l4PsA5q", + "uWUT7xEqPqg1PShxrr3d7N5G9dKVSlMCrj7AmBFdGJKllbcQlkGtFYr3FrulK/V6Z9OXsxarlYBPecZC", + "pHij/XXWBirBzif5fwEAAP//hjJN4p8cAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/server/api/harvester/harvester.gen.go b/pkg/server/api/harvester/harvester.gen.go index ac40e6a95..66355b736 100644 --- a/pkg/server/api/harvester/harvester.gen.go +++ b/pkg/server/api/harvester/harvester.gen.go @@ -26,13 +26,6 @@ const ( Harvester_authScopes = "harvester_auth.Scopes" ) -// Defines values for GetRelationshipsParamsStatus. -const ( - Accepted GetRelationshipsParamsStatus = "accepted" - Denied GetRelationshipsParamsStatus = "denied" - Pending GetRelationshipsParamsStatus = "pending" -) - // BundlePut defines model for BundlePut. type BundlePut struct { Signature externalRef0.Signature `json:"signature"` @@ -47,7 +40,7 @@ type BundlePut struct { // BundleSyncBody defines model for BundleSyncBody. type BundleSyncBody struct { - State *BundlesDigests `json:"state,omitempty"` + State BundlesDigests `json:"state"` } // BundleSyncResult defines model for BundleSyncResult. @@ -70,9 +63,9 @@ type OnboardResult struct { TrustDomainID externalRef0.UUID `json:"trustDomainID"` } -// RelationshipApproval defines model for RelationshipApproval. -type RelationshipApproval struct { - Accept bool `json:"accept"` +// PatchRelationship defines model for PatchRelationship. +type PatchRelationship struct { + ConsentStatus externalRef0.ConsentStatus `json:"consent_status"` } // RelationshipGet defines model for RelationshipGet. @@ -97,23 +90,20 @@ type Default = externalRef0.ApiError // GetRelationshipsParams defines parameters for GetRelationships. type GetRelationshipsParams struct { - Status *GetRelationshipsParamsStatus `form:"status,omitempty" json:"status,omitempty"` + ConsentStatus *externalRef0.ConsentStatus `form:"consentStatus,omitempty" json:"consentStatus,omitempty"` // TrustDomainName relationship status from a Trust Domain perspective - TrustDomainName *externalRef0.TrustDomainName `form:"trustDomainName,omitempty" json:"trustDomainName,omitempty"` + TrustDomainName externalRef0.TrustDomainName `form:"trustDomainName" json:"trustDomainName"` } -// GetRelationshipsParamsStatus defines parameters for GetRelationships. -type GetRelationshipsParamsStatus string - // OnboardParams defines parameters for Onboard. type OnboardParams struct { // JoinToken Join token to be used for onboarding JoinToken string `form:"joinToken" json:"joinToken"` } -// PatchRelationshipsRelationshipIDJSONRequestBody defines body for PatchRelationshipsRelationshipID for application/json ContentType. -type PatchRelationshipsRelationshipIDJSONRequestBody = RelationshipApproval +// PatchRelationshipJSONRequestBody defines body for PatchRelationship for application/json ContentType. +type PatchRelationshipJSONRequestBody = PatchRelationship // BundlePutJSONRequestBody defines body for BundlePut for application/json ContentType. type BundlePutJSONRequestBody = BundlePut @@ -197,10 +187,10 @@ type ClientInterface interface { // GetRelationships request GetRelationships(ctx context.Context, params *GetRelationshipsParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // PatchRelationshipsRelationshipID request with any body - PatchRelationshipsRelationshipIDWithBody(ctx context.Context, relationshipID externalRef0.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // PatchRelationship request with any body + PatchRelationshipWithBody(ctx context.Context, relationshipID externalRef0.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PatchRelationshipsRelationshipID(ctx context.Context, relationshipID externalRef0.UUID, body PatchRelationshipsRelationshipIDJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PatchRelationship(ctx context.Context, relationshipID externalRef0.UUID, body PatchRelationshipJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetNewJWTToken request GetNewJWTToken(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -231,8 +221,8 @@ func (c *Client) GetRelationships(ctx context.Context, params *GetRelationshipsP return c.Client.Do(req) } -func (c *Client) PatchRelationshipsRelationshipIDWithBody(ctx context.Context, relationshipID externalRef0.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchRelationshipsRelationshipIDRequestWithBody(c.Server, relationshipID, contentType, body) +func (c *Client) PatchRelationshipWithBody(ctx context.Context, relationshipID externalRef0.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchRelationshipRequestWithBody(c.Server, relationshipID, contentType, body) if err != nil { return nil, err } @@ -243,8 +233,8 @@ func (c *Client) PatchRelationshipsRelationshipIDWithBody(ctx context.Context, r return c.Client.Do(req) } -func (c *Client) PatchRelationshipsRelationshipID(ctx context.Context, relationshipID externalRef0.UUID, body PatchRelationshipsRelationshipIDJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPatchRelationshipsRelationshipIDRequest(c.Server, relationshipID, body) +func (c *Client) PatchRelationship(ctx context.Context, relationshipID externalRef0.UUID, body PatchRelationshipJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchRelationshipRequest(c.Server, relationshipID, body) if err != nil { return nil, err } @@ -348,9 +338,9 @@ func NewGetRelationshipsRequest(server string, params *GetRelationshipsParams) ( queryValues := queryURL.Query() - if params.Status != nil { + if params.ConsentStatus != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "status", runtime.ParamLocationQuery, *params.Status); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "consentStatus", runtime.ParamLocationQuery, *params.ConsentStatus); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -364,20 +354,16 @@ func NewGetRelationshipsRequest(server string, params *GetRelationshipsParams) ( } - if params.TrustDomainName != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "trustDomainName", runtime.ParamLocationQuery, *params.TrustDomainName); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "trustDomainName", runtime.ParamLocationQuery, params.TrustDomainName); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() @@ -390,19 +376,19 @@ func NewGetRelationshipsRequest(server string, params *GetRelationshipsParams) ( return req, nil } -// NewPatchRelationshipsRelationshipIDRequest calls the generic PatchRelationshipsRelationshipID builder with application/json body -func NewPatchRelationshipsRelationshipIDRequest(server string, relationshipID externalRef0.UUID, body PatchRelationshipsRelationshipIDJSONRequestBody) (*http.Request, error) { +// NewPatchRelationshipRequest calls the generic PatchRelationship builder with application/json body +func NewPatchRelationshipRequest(server string, relationshipID externalRef0.UUID, body PatchRelationshipJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPatchRelationshipsRelationshipIDRequestWithBody(server, relationshipID, "application/json", bodyReader) + return NewPatchRelationshipRequestWithBody(server, relationshipID, "application/json", bodyReader) } -// NewPatchRelationshipsRelationshipIDRequestWithBody generates requests for PatchRelationshipsRelationshipID with any type of body -func NewPatchRelationshipsRelationshipIDRequestWithBody(server string, relationshipID externalRef0.UUID, contentType string, body io.Reader) (*http.Request, error) { +// NewPatchRelationshipRequestWithBody generates requests for PatchRelationship with any type of body +func NewPatchRelationshipRequestWithBody(server string, relationshipID externalRef0.UUID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -647,10 +633,10 @@ type ClientWithResponsesInterface interface { // GetRelationships request GetRelationshipsWithResponse(ctx context.Context, params *GetRelationshipsParams, reqEditors ...RequestEditorFn) (*GetRelationshipsResponse, error) - // PatchRelationshipsRelationshipID request with any body - PatchRelationshipsRelationshipIDWithBodyWithResponse(ctx context.Context, relationshipID externalRef0.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchRelationshipsRelationshipIDResponse, error) + // PatchRelationship request with any body + PatchRelationshipWithBodyWithResponse(ctx context.Context, relationshipID externalRef0.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchRelationshipResponse, error) - PatchRelationshipsRelationshipIDWithResponse(ctx context.Context, relationshipID externalRef0.UUID, body PatchRelationshipsRelationshipIDJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchRelationshipsRelationshipIDResponse, error) + PatchRelationshipWithResponse(ctx context.Context, relationshipID externalRef0.UUID, body PatchRelationshipJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchRelationshipResponse, error) // GetNewJWTToken request GetNewJWTTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetNewJWTTokenResponse, error) @@ -692,7 +678,7 @@ func (r GetRelationshipsResponse) StatusCode() int { return 0 } -type PatchRelationshipsRelationshipIDResponse struct { +type PatchRelationshipResponse struct { Body []byte HTTPResponse *http.Response JSON200 *externalRef0.Relationship @@ -700,7 +686,7 @@ type PatchRelationshipsRelationshipIDResponse struct { } // Status returns HTTPResponse.Status -func (r PatchRelationshipsRelationshipIDResponse) Status() string { +func (r PatchRelationshipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -708,7 +694,7 @@ func (r PatchRelationshipsRelationshipIDResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r PatchRelationshipsRelationshipIDResponse) StatusCode() int { +func (r PatchRelationshipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -815,21 +801,21 @@ func (c *ClientWithResponses) GetRelationshipsWithResponse(ctx context.Context, return ParseGetRelationshipsResponse(rsp) } -// PatchRelationshipsRelationshipIDWithBodyWithResponse request with arbitrary body returning *PatchRelationshipsRelationshipIDResponse -func (c *ClientWithResponses) PatchRelationshipsRelationshipIDWithBodyWithResponse(ctx context.Context, relationshipID externalRef0.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchRelationshipsRelationshipIDResponse, error) { - rsp, err := c.PatchRelationshipsRelationshipIDWithBody(ctx, relationshipID, contentType, body, reqEditors...) +// PatchRelationshipWithBodyWithResponse request with arbitrary body returning *PatchRelationshipResponse +func (c *ClientWithResponses) PatchRelationshipWithBodyWithResponse(ctx context.Context, relationshipID externalRef0.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchRelationshipResponse, error) { + rsp, err := c.PatchRelationshipWithBody(ctx, relationshipID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParsePatchRelationshipsRelationshipIDResponse(rsp) + return ParsePatchRelationshipResponse(rsp) } -func (c *ClientWithResponses) PatchRelationshipsRelationshipIDWithResponse(ctx context.Context, relationshipID externalRef0.UUID, body PatchRelationshipsRelationshipIDJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchRelationshipsRelationshipIDResponse, error) { - rsp, err := c.PatchRelationshipsRelationshipID(ctx, relationshipID, body, reqEditors...) +func (c *ClientWithResponses) PatchRelationshipWithResponse(ctx context.Context, relationshipID externalRef0.UUID, body PatchRelationshipJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchRelationshipResponse, error) { + rsp, err := c.PatchRelationship(ctx, relationshipID, body, reqEditors...) if err != nil { return nil, err } - return ParsePatchRelationshipsRelationshipIDResponse(rsp) + return ParsePatchRelationshipResponse(rsp) } // GetNewJWTTokenWithResponse request returning *GetNewJWTTokenResponse @@ -917,15 +903,15 @@ func ParseGetRelationshipsResponse(rsp *http.Response) (*GetRelationshipsRespons return response, nil } -// ParsePatchRelationshipsRelationshipIDResponse parses an HTTP response from a PatchRelationshipsRelationshipIDWithResponse call -func ParsePatchRelationshipsRelationshipIDResponse(rsp *http.Response) (*PatchRelationshipsRelationshipIDResponse, error) { +// ParsePatchRelationshipResponse parses an HTTP response from a PatchRelationshipWithResponse call +func ParsePatchRelationshipResponse(rsp *http.Response) (*PatchRelationshipResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &PatchRelationshipsRelationshipIDResponse{ + response := &PatchRelationshipResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1082,7 +1068,7 @@ type ServerInterface interface { GetRelationships(ctx echo.Context, params GetRelationshipsParams) error // Accept/Denies relationship requests // (PATCH /relationships/{relationshipID}) - PatchRelationshipsRelationshipID(ctx echo.Context, relationshipID externalRef0.UUID) error + PatchRelationship(ctx echo.Context, relationshipID externalRef0.UUID) error // Get a renewed JWT token with the same claims as the original one // (GET /trust-domain/jwt) GetNewJWTToken(ctx echo.Context) error @@ -1110,16 +1096,16 @@ func (w *ServerInterfaceWrapper) GetRelationships(ctx echo.Context) error { // Parameter object where we will unmarshal all parameters from the context var params GetRelationshipsParams - // ------------- Optional query parameter "status" ------------- + // ------------- Optional query parameter "consentStatus" ------------- - err = runtime.BindQueryParameter("form", true, false, "status", ctx.QueryParams(), ¶ms.Status) + err = runtime.BindQueryParameter("form", true, false, "consentStatus", ctx.QueryParams(), ¶ms.ConsentStatus) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter status: %s", err)) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter consentStatus: %s", err)) } - // ------------- Optional query parameter "trustDomainName" ------------- + // ------------- Required query parameter "trustDomainName" ------------- - err = runtime.BindQueryParameter("form", true, false, "trustDomainName", ctx.QueryParams(), ¶ms.TrustDomainName) + err = runtime.BindQueryParameter("form", true, true, "trustDomainName", ctx.QueryParams(), ¶ms.TrustDomainName) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter trustDomainName: %s", err)) } @@ -1129,8 +1115,8 @@ func (w *ServerInterfaceWrapper) GetRelationships(ctx echo.Context) error { return err } -// PatchRelationshipsRelationshipID converts echo context to params. -func (w *ServerInterfaceWrapper) PatchRelationshipsRelationshipID(ctx echo.Context) error { +// PatchRelationship converts echo context to params. +func (w *ServerInterfaceWrapper) PatchRelationship(ctx echo.Context) error { var err error // ------------- Path parameter "relationshipID" ------------- var relationshipID externalRef0.UUID @@ -1143,7 +1129,7 @@ func (w *ServerInterfaceWrapper) PatchRelationshipsRelationshipID(ctx echo.Conte ctx.Set(Harvester_authScopes, []string{""}) // Invoke the callback with all the unmarshalled arguments - err = w.Handler.PatchRelationshipsRelationshipID(ctx, relationshipID) + err = w.Handler.PatchRelationship(ctx, relationshipID) return err } @@ -1241,7 +1227,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } router.GET(baseURL+"/relationships", wrapper.GetRelationships) - router.PATCH(baseURL+"/relationships/:relationshipID", wrapper.PatchRelationshipsRelationshipID) + router.PATCH(baseURL+"/relationships/:relationshipID", wrapper.PatchRelationship) router.GET(baseURL+"/trust-domain/jwt", wrapper.GetNewJWTToken) router.GET(baseURL+"/trust-domain/onboard", wrapper.Onboard) router.PUT(baseURL+"/trust-domain/:trustDomainName/bundles", wrapper.BundlePut) @@ -1252,125 +1238,125 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x8aZObyNbmXyE074eZkG12gRzxxhvsAgkkEEhCVz0dLMkisYkddfi/TyCV7apy+bq7", - "7xI3pu0vppJcnnPOc/JkHmXy28TL0yLPQFZXk4+/TUpQFXlWgfsfPAicJqnHRy/PapDdH52iSGLPqeM8", - "g89Vno1llReB1Bmf/qsEweTj5H/BX/uFH28rmClioSzzcvLp06d3Ex9UXhkXYz+Tj5P7C4jZyNBXCGOt", - "p7Zj11+ajyB8Px5bOsmmzAtQ1vEIOXCSCrybFM+KRug+GP8P8jJ16snHSZzVM2LybpI6fZw26eQjOZ+/", - "m6Rx9vgLRZB3k3oowKMqCEE5+fRukoKqcsJ7T6B30iIZ3zOQC5ymjoMmgcBdgs/V3n0dr6rLOAsfA65A", - "FtbR5CP2bJCn96O0Jbg2cQn8yce/PXB/HfeXL/Vz9wy8esTENpmfAD4OQVW/BBYgBDnzKQf4CE0Dao4C", - "YoYiHu5hjj/DnQAQM4ABiqLmNB34rjfHKCRASeDNKRR1CWzyDbrPo22a+g8aoIrDzKmbEvyIH9svFUfD", - "x2EWZ+Gv3thRMBLuh+25Z1U/vZvUZVPVv7p31D9qao51HwJ+bernqRNnv6spf6+qOSn4xo5fxX/V7yuE", - "b4v8fbNvh8xjc3/4o9aof4cmHyNUD2ZV34pUO3VT/QCbAaqnyeNfje7dpCl8p3708DutPCJ8U67REJ+7", - "+76AXwb/rni/R4Qnz/307qvn/vaCI7+ik48TmsZxksYohEJIMAsoAiCOCzDUITyPnAEamyMzek64KIoC", - "2pu7uOPNXAwn5zjigRnhv+Ldr9jk48SnHYB5Lg0AIIHj0h6KBriLE/gcOISDOQQyRwEymxEzGqNoDAUA", - "nc9cipzRDoES3jd94pOPE5QkgllABXPCmSEYhVGkR4Bg5uLABQhBzQBKzl3X8XEfCQJkhmM+gRIu8MDc", - "9wA5c++O91rd3Ev3fxk0Dh9IZA49cxcozqCNoEJPc+8zvU7ej/9YQZI1iBMMUxZljjGFe+kpU2V5AZsc", - "x4J9yHQyy4Sy7rC2gMMqQh8WNpdpu9QTWe/MaGx4uUaXWJp3CMvolcjw7HDKVL3qON3md7ouCZ2ys27C", - "WmU6iUEtgWM6cSftCPug9gLPrNlQ27GMp7JI1PoHDXExoj9lgslsHm9ylRM10+RY3sWVTt0S3Yq598zz", - "3M60kK6xsXktC7u9/KinuJmRnDIvRZOjlES+ZIU6IoRWorGyKN9UljjwptypvN6pJtNpZnhT0Xws61Xe", - "67Xzo+yUqWjehS7SczdGeWCxTSbZmapOdPwDg8wzO+t4iCLvJugqQ9wlZLtusZXm6CnzcKN1z4KhMvRD", - "9rCTLVRTZUFrvYzpxTNjPXq2TN4i9+qZ6da8gKmmPmi82p8ykWe2jxqqyuE+7g/kzcMeMqsG0kndHceG", - "Zw3dSxPMPhiJLMyHIyY2zqGITpkvJSOGg8paEjdUEqPrbHj2aCYUOJ45ro+HY3SUhF64MQYbViUbCgJj", - "y/iGkVmmV7lTttupXRgKscogEre9SlvZxXldYBndYhhCZvmOGd8vmVxmGZ1fRMC4uC4qch7VG8uqPmXd", - "ElFkyVnadE0p7hZzdcyd2bLCh9mike3FlS05a0fNc5DEl0t+MS5i67WFs4wzccHri1NmFXtBnhmWYNjp", - "lgvxNb2PCaxZezuMJY+Omx64S+f3Nil4CYmyrkpbmeTnjOT6Whob6SnbpubZq6ZJpPYhEYj2LGGLWNiJ", - "sWSdJcOYzlBjRq1uM4tYKmCleVyKUHon2ks2LWKEDk+ZP4Tb1vCtjiSVvCiBf57upPpsXVgiEk1C0g9w", - "GNWzuZFcb/CUbhBf0C9RYzWNV16dZMQgDQS+MDo24JdiZ4O9SnEb1ScB7K+nNULX9MY933am2ZKRznOV", - "YMs7zKQYUZ5vPa1XT9klomAmVFmGkc5hqLGqLPMbkwlGjiy2qiDxzD5kt3C3uy7g4TzTTXxeI/Bl4UxD", - "excWp6w1WZgNw9HOIqt7LKMbN3UhdKZuy8vOZlndWqjMUtL3EeIvmNlqmOM+7jUerlWrVGtPmbudD8cD", - "23pYgri4Qq5QzTQlrXW3qOnvFV7fouIuRkffrEevW5l6tzbt2jqrjY0ryClTOUbiuJGLlsjeGDaKjNxf", - "GN06plsX027eQv0ynvtZOkN4SBfW+Cl7jsi15cXX2uyTLhhhz7N7lfEkdg9YnhHYO3+Hq+AwknTK5pnH", - "sbrAqnwn8dyTX1wvHaOrLMszlcrlXzF2MitG5B2jd8vbFe6PGJ754gpXEk+a35yD0XrZpVuMs5+BJCxr", - "dyLzVbNMJ3/p9ZSxncqqQjjODf6iM1iVp7uNw1A5n0oa9kX/Zy/tb6tMu7kceXYxpB3nkHHUU7baaah9", - "0diVtduvduP8h24tRKg1niG1GN2qA3n20u4znjXL2oLI8Ixoyc6tI8tTdpQXTpEZvWxlRTeVVk+zmM93", - "Agt3usB0spjzHMccEImLH3pCswvHMrIQhmJ9ylhZZh1dzJiFx8yTwVrNRVzlZGvHhrKqGPtzo2lCf7m1", - "c1pdDczqJlD9ca0yDCP2KhLlp8ztGIZlVGbLsxITC8ysB0msGbR0gWd4YfvZFm7XPcydi1pQhZae7/cR", - "CjflXhY4WeeHU8aWYGFhJH/rmovuGPq5289I8ri6XLmsd3t9b8RrkJ7nCsOijKKHLTtbozZaxQs1CPMq", - "PmUrBjewCwpcYWpt9gvXjOe26aw4hmFYz9RkR+sYhtF5RrA7g5FDyRCI7ua4muHz9OUKn7JW3OC1DrAo", - "RXoyOzRJ3kWE7HZ4cuFk0XZhPNnyRbKlGM8gyumh2NfCcmuKeyXVOMP1TtlBaUrMkFhmYTFUxe2oHB2O", - "zHRL0GtSor1tjiVX7lCvnCi31uvjoqraug8uzzRJP2nSOLMCE7MzuXXzfVXhBiHXu+4M3ITi8SEXnQOi", - "8RHm76Mo7Li+XHRyyAVX6pTlnsqR9RQ9x6RK9s4q3XCEPN0fcBlmjMt+O8RrSta9jtdtZZkf5aj1NEYX", - "VqzO8GEos6eM4UDTlISeNedrGjbbcmHhaRRMPSX3b6auXXOi9sF0w6MwEH2bEVYN3YtThKmpXok39imL", - "SWPZxcmwIWftFI9tzJwnHbWlTQUh0N0qcuRlgRLqbWvdjAHka6ZSKJ3hVS5ZLC0+GeOFhRVak9O0PYvD", - "vDVxt8o6RYsFXbsO6XZrR5e6Q2rHb/Lr+XrIkFlY7eJ8b+74w1D55Cm7Cj1Rzyo5lD01xWb2Am2VgtOF", - "aFl42IBQoXG5JOzRqNWzGbWEdxgG9UA1puebFKOwm1PWgDjg8h1GKv2hyWmfRPF52G1QlgGUzO42PdZQ", - "Sw22hvXBP6adGsBmKkod73NBNSwC+JQdKxbrVov8Zto5s0v1uZhbqLIKvV3cXpVpqyVstDhESa/6GnKm", - "EWOu3WaCHCb6GSzxNX3KZNgTpRRm6SmBReuEk/350a8zX/EMZXeOkY5Hrh1oOSdg5mclWbTwuRKm8ty6", - "zbyCG6JTVnXTpBT93gqvFkk7/RUs6bloTLWcuCKyvJ4qMVoqy3KeXbYswl4P+W2XCajNwstV68vVKWvs", - "o9JcXaxYXprp7WbOQqtbWOaxZWNtXR9WhNZ3Hrw0qf1tvfWxboMiukzzy5Bog1jjq1O22Kcs6hHLczwL", - "1yFDNlvr5kjpFW6JXeYtSaucZvOVG2TBysNohQxqWMrrOFMH/oLHTnnKRBSxk6u3TsEBbcR06foxfMhL", - "KblwuSriJt/TZVrMeTZm4VN2XwgLGv/G4vh5LqMA6VupAWVvvsw/gEGJXMmL17EiWzcZ1WK5kjOD9Dh5", - "Jl+Kw45T5h/AoNz8vRyvY7lXzyqimTa+5i+dHHexm4r1cXuv3DoSERrSPBnLnb2IyOe810wBU88qqfLy", - "EOgftkGy7DtD2apguRQx3SSCrlCBEuCzzfoyG5Tdr46vV1VHes/FOXf1y7wMgcxn7yaFU9egHHcY//dv", - "zvsb8/6IvJ+fTu9//WX6P6fTh7fK/vfrwv/zP//1HU39qY1xnV/AD1MRoxle72kfDd/aya4zN3dK/18M", - "52mj+EiQyPyP2liWzH9Hhtc9vSWTAZJ7ZrKK4uKP5glL4NTA/9V5lUrDEAx9j6DvccRE6I848hFBjs85", - "5Ds1eF/HKXiV4UPfsH7s/z75X+2unV+9PKue8q/+56zskxRPg7h5ngAne6PtnxzU/QcGdf/AoI9ky79S", - "76/YFPuTd8+t/QLCW9p7S7jvWui7WvwRXZmiKPPWSf4gbR3PA8XvstErNTw1/BEsCdw7j2uQ/jCr9cL7", - "Pt3NIj/akc/y3U5ZOsP4evs8MfzV8I40Ox5w5xhMZ3UIDwZ/9I2tVqv4PLkd99pwPBjKkUcVe4+aX/7m", - "jmf/oAzHPYnspKQ+7jTE3qPdxhRQ7SYMqml1a9NKj4eocw5Kcq9jIv2aDzHN9FCVv6BKpkRuarSuiQzq", - "mcHUs/Xfb03hz3PFP1NSP1NSP1NSP1NSP1NSP1NSP1NSP1NSP1NSP1NSP1NS/wkpqde/9f/JH+pfdTNu", - "Z37we/2LAy8TbbVQmuKY0ctezJV0Gys8t5laTb5V1kmkLtGFLZ+9mb2hyBag5MZTFep2RZWVcRBvh1pF", - "bgGy9VbuEc1sW2r1NNxPJYXpyU2Vbq94il7OZY8EosIjAn/dHSPnlttyXtHExqGvEuztAUJWaLnIbZvE", - "uzWGo0fpUl8W5GyZDf6Cx7oOBIPOFcx/f+cIyt/dqEDj2qe5cRxzVTlxMTWJXWQpKWzz3hyjrjJDztFO", - "POvw63Ur11mMKktH6B/dpkDLG1N83qXIlyjxsT7xF3poSeLZwcThyLHiuBvxMnZwDloyrvvdAxu52aVf", - "nBkPejSuVNGKdGPLLux9Hx0XSnHcd6G1UFon3Z19XnBV9nJHxXTd1sPE2pP6ZLXXBuh4MIpjmpzHdf/T", - "zuam8uqgmgKh3sLbejfubNSxrF/zX8q68Hi572ugP7OxedrWlCp3kaCnfU28lXa4K+0Qn2N1e6+V9kG5", - "yMKu8aXd4C2UwsOsUMfmtSeJDTAFoLIPRUNc1+22IivKgh+5knjx0iRxOVb30vn1uNcQ1ag6SX/a8rHK", - "bdxcu5JV27iS+FKSQs5eG3d5f2CPwtEwbc4twnGjZR9BLd5HPbdtlbXTURGs5vr5qmLzOF7tj+WUx3IK", - "0640ejRUbVMYgpEvNOpGLB03V8qIbKfQdNDLOd1wmn25MDxN76+b5MBHZBQUImt7qiN0K8xN2WnqifAe", - "ZY7r3M4TammQfn+Yigzk53mZwOWuUx0O21gLwkrPhLrZwqvqtudaCpM85ByVS5WxJKw4j+tveLksV41B", - "VFhX3iC7J3QM1aINtZ4ppSJEAmsLFtpPm/LCNVnjMTdEQU2DXbX1zSKrtgiwHnGWw6yDwXCbQUJqXqYd", - "vWl7Iulyth+cUl2wzIpdeCHJSLumsDzq0HAaLZPJWgcEn3IGRV56Md+IOgEgqr0SR0VcPHYnwnfWBwLL", - "sEKx5FODHmjFcLF0uy0yTNA5SPUvrnRA9qpeYvwStrPSShBqKqdW2q1Vt+CuzVqxEZtJmBndX0hYr2+B", - "TPMBh1W8LhwgqVMuyNnId1jmI7tyic9vzK2dzTHZaMsBRhZ+jyJIGtDipUul/AZ7Xmr226k0kJjBG1No", - "unXhgGHyOO332KI6uE0WM5jsdhdXU8sSma6jjasc2TWOCld/jx47Eovm/aH2thTTrETIZ90qPe8PoiLs", - "cXzFq4KOB+djvDW8axQGu16Vg1q/xUKC1jtaonTlUMYXdebkzcoWtxrk4QpRKwhMzj3Oj6QDkgOja1f4", - "SjzQCjFrUFEkvICtMje3E1UIcHu5ZteHNO6dhQPC/4a+G7len/n7OPkmdYq9jiD6LRr6su1nrubDUm40", - "ToPkm6zPLSxQwCypJBfT5Gm/tKaXg7WZxc0x6MhgrgtREgYrn9b9sKbws1CkxYxIorNPLGDUuLiHUPSN", - "KoAXB67QYSa/5o1RCVExpHLUrQKP8BiFU+JlH6K3qtxMUXRN3uZo65tVkTeudXSMWuy5KYfmLDalPCv/", - "J0QQQYUPAyF5Qp8tDrlNBQmNY0I+Y9HjXzeCXAYteR1B7mV/8QjCH+Wm0o2ZwGP63oCYahmdATrfWTvO", - "y2w1Fi3ev5iiu9iqyxZ1JIm4CgeyuMWFHl4joJVmT+ZusraP1JlDaCgP6gNPXXAwOx4deJfhEukAYSmt", - "cMzDzsuqm7IirVCRnll7aojoeoOVSDlsWr5dTEM/gjJK1LerQlxgwbzcxefGv2jw+sIvIo1aMkXUqkxG", - "eKW3m1azTc8Nx8w8tkUcu+KFuFiGDzkcNvCy40W2JDB0Em16Gw+oxRKmWUfIUjybsmw7U29+tD23Q18E", - "cO9RuwNy2GYXfnOMoCK7nd05WN+qy7pdXw6HI0hyBGeXs40B4CZygj1+7ubzbMUMuaI4vcmL9MZOFF/d", - "9SGYQVjVonFg8D+MIKv18Yhsw3y36y2wvK53iDZTRBaStuS8vhQOTK6qvb61UX+f056OdH4xvwzo4nYY", - "Nvtj2m+ng6fUmhHtjwdOCkNV1kpJXlUQf6yrC0dek92wY+BlFBuN4HpYLQdHQoNho9GlBYVRM6SFj7ST", - "741NgjOyekgshKLXmgSFNp7kUwTdaXXbuMSctdorjNYpbBogMtPOsoSF3q50Wrxl/M324oW/PWoetQZ7", - "JlL7NbRU0pWvuxRGXfeNsSqwaRXFg7ueFecsI2H1zA6XnSvm2lVhHb8JLT5aJCG8X4tevK4PM2i+cepF", - "kazNfXOZt1OaK1UxUZBBjDkcBZVowhs7Gkxz0PmrjzG5qfoJWaUVic9vpEP/YxEEfx1BXMJ19/L6fGls", - "AG6qGO93uLvmiPJ41R1GSJCb78WJNVUXJBAk3vTzdD0XtEu9TWDKpr1rKyqWyHZJId6iAzNVqN1eirY3", - "imiWW980TSLoRPpaIOWqQaKVLcYpYhDlWtHELsSazloPNk9UYUbKy+B41I8O7dtgYzah2ZuFMej52bpl", - "NMqm/4wIwnp0c27TdLPezchoSchh3dXLhsp06a8aQTSe6R6/rnyNIE9lf/UIYsOMbU2nRVmzkQZdUYxh", - "LiRl+pod+sJqtsVrbZrlWRkmOTLnm+lwLfnmykSbtvZEVKz3W3e92bFuKguLWQuVVKjtKoZnCX8zn+4M", - "2XVMW8uKajefX0t0383afb3OWnXhbqJ2t1GyTLxKwbHODK9teQJKkq1WF8wR19diUB6OeCJZzhlr1w2V", - "9d2SLkIrppvbjAJGtVilMrGdcvR1uw4OZ6kR0wiiaDzdOwddH6iKtMI4lnZ7rqt0QKvodZr03O64t/G9", - "PYD1dCsYLkvDK7Wbb2PSFi9b14f2s42JGT3cArsXNkmpTpcuPkvOy1vq+fS+A0ghI2Sqn320lpagntP4", - "0ViQnpAtWL0LOejqhCJl1LcfRhCVJtp5CrOEi6GWSkWBTWyNpoWyQtB9sthud5FxRFGNOExRzHbwYUZF", - "g3amrjhZ7xhZTc8ekR4okif8dl1sjhdV2MOXQwldp8M6wS/UCu6a9rYXY5nl4MtZp1lSazvG9dGuCLDr", - "+axL63K3QGg1PqyPa6Of0/N9bQYQ0q72KL3WULnbtBtmKrZs4C1vCpcAjcStOE6Z8+GKoth1emk7amUd", - "fQ6/AQEJNhnSCHPoup7to8Tag4sv1Tu2v+b0bWUDVvf6Fav2QrUF9lzDLapaHQo4bmxCjcg2iIbFTa9g", - "UYdUYQCLLbpFd0vF2y/ZlkQpAV1Pb3uVDFMOodYzRD0f/WF/PrhnfZ3regILjLXCj0TH6H8wgrx18+Kt", - "zNf/5xe/fnhz6w9f1Xp9OezFQYd7nx8eUfuDl6c/vjRI0G9kOu8HaV50jAcOTQYz4j1JodR7gpxh7108", - "8N5j3nyGB7OZEziz54M1zf1Ey7Oh8Fen4JD3c+d98Mtv9Kf3X56J3/GMYp/eOAY3Whl4TRnXw3a0w4Mu", - "kVO2oKpB+avTjBi+Y7iXDeFXre5XS+MsyD/fWnW8+/mV7K79iRTXUeNO3k2aMpl8nER1XVQfYTi8F482", - "gBegS0Bdbxzv4pQ+HDqJ45cxSCbfXFmVPr+CtqBsQQktPkO532OtCuA9qBHn2YfJu0kSeyCrwDM0TOF4", - "EYCwD8gLRB9huOu6D8797Ye8DOGnphW8kjlB2wrvsQ/Ih6hO76jquL5b/Qd43kPrAmTjE34frwVl9RAE", - "/YB8QNGxq7wAmVPEI4U+IB/wyZ0E0d06cPnscM+9JHycDBo9/v5C9kcQoDZeVBy7KJ0U1KCsJh//9tsk", - "Hoe8NqAcJu8+K+LpQuG7ZxeJQdakX48pgZGfPsji+0MBMn8k0i9vMOv1gZznsKHHOFBQ5inkQHfnhB7e", - "CRWgHC1Wx+3o32+BrF/58rvfee352wuiv7x7edEaQ5B/2iXr12e33rhrvW08D1RV0CTQF+M96P3lvvdb", - "I3yBDH++GP7cke+2fe3Cf/tllLVq0tQph8nHySquaqiOAPSCTKNz1E440mPykjy/jCO8ZB782/M/Zf7T", - "CLdwai/6loybsfhFj8aLtt+S86WiZB7Kg2/wfqbH6Bpf2VG+7vlrJKnLBvxesjydf/3l0R5U9ecrvv90", - "cnw5b/jpZdwb0X76NxH0P4udzH2qgflxmqlemBx6Mkb1A6bep4j3j4gOn7v6702TGuiUvWk+HW3+l6n7", - "64Hz/yhdS6CGHKgEGeiADyl7E7of8oa6uI7uHlc5KYC8xInTCnKqe1FexmGcOQmUZ+CZIcbGDzW+YYT8", - "ccj9mSFeuXgNNRWoIAc653H2BKLOoRF1XsY3cB/5izTQWCcC0Dex1sl8KAQ1FNfVXRrmrljos3lfWv/p", - "5P2Pph/lBSIXjEh9KMhL6EmqxwrxrVg1CvOVWt+biF4Hz39lXHp52+ANLhqgbsqsgpwMch7Ke1P00TAg", - "q+8gsvCrZSoozyAXRE4SfJ60n8f3D3+e1l84u/6idciBMtC9XEB8hxnPiPrZ7G/Q9LdXi4tP8GOrcTdD", - "0bwxg3z9GscPWPQCZPZYuLwRv75d3fy5APad1c4/P5Z9VcAbZHq8/Mydx/H+ye+Mcv8pU6RVJLnjP1Ht", - "bh7I/SLWfYp8TbCHqZ++UPGHaAZXT2dkirz6Ltnu52j+0mz78s2VNyj39EWQcfJxkgQKgD9qEPhPRhtD", - "mFO/nDK8pixBVicDdMny7r4O/vetw775UssbMol5CeIwg14QC/r8eZR/ixeMAKMyz+IbqN5Q6n298Hfm", - "3G9c4j7+WOnB3pd5gCT3nCTKq/pD1TlhCMoPcQ47RQy3+GSE9dTra9KvPzvL06oR+HcXfe6yoPciJwvH", - "lUbmQ9UXoR7zyRe/eAn3293ss5HGYPgY4uHcLxcFT/19Djl/t6dnmF8sel1Qd2CMwM9Gqb72/XIB/OmX", - "T/8vAAD//17fawRNTAAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/server/api/harvester/harvester.yaml b/pkg/server/api/harvester/harvester.yaml index 1074853c1..69db1446c 100644 --- a/pkg/server/api/harvester/harvester.yaml +++ b/pkg/server/api/harvester/harvester.yaml @@ -47,7 +47,7 @@ paths: default: $ref: '#/components/responses/Default' security: - - harvester_auth: [] + - harvester_auth: [ ] /trust-domain/{trustDomainName}/bundles/sync: post: @@ -79,7 +79,7 @@ paths: default: $ref: '#/components/responses/Default' security: - - harvester_auth: [] + - harvester_auth: [ ] /trust-domain/onboard: get: @@ -89,12 +89,12 @@ paths: description: It uses a join token to authorize the harvester in the Galadriel Server and get its JWT Access Token operationId: Onboard parameters: - - name: joinToken - in: query - description: Join token to be used for onboarding - required: true - schema: - type: string + - name: joinToken + in: query + description: Join token to be used for onboarding + required: true + schema: + type: string responses: '200': description: Returns an access token to be used for authenticating harvesters on behalf of the Trust Domain. @@ -120,13 +120,14 @@ paths: default: $ref: '#/components/responses/Default' security: - - harvester_auth: [] + - harvester_auth: [ ] /relationships/{relationshipID}: patch: tags: - Relationships summary: Accept/Denies relationship requests + operationId: PatchRelationship parameters: - name: relationshipID in: path @@ -135,10 +136,11 @@ paths: schema: $ref: '../../../common/api/schemas.yaml#/components/schemas/UUID' requestBody: + description: Relationship status to be updated content: application/json: schema: - $ref: '#/components/schemas/RelationshipApproval' + $ref: '#/components/schemas/PatchRelationship' required: true responses: '200': @@ -150,7 +152,7 @@ paths: default: $ref: '#/components/responses/Default' security: - - harvester_auth: [] + - harvester_auth: [ ] /relationships: get: @@ -158,16 +160,16 @@ paths: - Relationships summary: List the relationships. parameters: - - name: status + - name: consentStatus in: query schema: - type: string - enum: [ accepted, denied, pending ] + $ref: '../../../common/api/schemas.yaml#/components/schemas/ConsentStatus' - name: trustDomainName in: query schema: $ref: '../../../common/api/schemas.yaml#/components/schemas/TrustDomainName' description: relationship status from a Trust Domain perspective + required: true responses: '200': description: Successful operation @@ -178,7 +180,7 @@ paths: default: $ref: '#/components/responses/Default' security: - - harvester_auth: [] + - harvester_auth: [ ] components: responses: @@ -189,6 +191,14 @@ components: schema: $ref: '../../../common/api/schemas.yaml#/components/schemas/ApiError' schemas: + PatchRelationship: + type: object + additionalProperties: false + required: + - consent_status + properties: + consent_status: + $ref: '../../../common/api/schemas.yaml#/components/schemas/ConsentStatus' BundlePut: type: object additionalProperties: false @@ -293,7 +303,7 @@ components: type: object additionalProperties: false required: - - status + - state properties: state: $ref: '#/components/schemas/BundlesDigests' @@ -327,15 +337,6 @@ components: properties: token: $ref: '../../../common/api/schemas.yaml#/components/schemas/JWT' - RelationshipApproval: - type: object - additionalProperties: false - required: - - accept - properties: - accept: - type: boolean - default: false RelationshipGet: type: array items: diff --git a/pkg/server/api/harvester/helper.go b/pkg/server/api/harvester/helper.go index 28d80b3d4..d96775a0b 100644 --- a/pkg/server/api/harvester/helper.go +++ b/pkg/server/api/harvester/helper.go @@ -14,10 +14,9 @@ func (b BundlePut) ToEntity() (*entity.Bundle, error) { } return &entity.Bundle{ - Data: []byte(b.TrustBundle), - Signature: []byte(b.Signature), - TrustDomainName: td, - // TODO: do we need to store it in PEM or DER? + Data: []byte(b.TrustBundle), + Signature: []byte(b.Signature), + TrustDomainName: td, SigningCertificate: []byte(b.SigningCertificate), }, nil } diff --git a/pkg/server/datastore/datastore.go b/pkg/server/datastore/datastore.go index fbb7d2e25..e77d2eff3 100644 --- a/pkg/server/datastore/datastore.go +++ b/pkg/server/datastore/datastore.go @@ -532,8 +532,8 @@ func (d *SQLDatastore) updateRelationship(ctx context.Context, req *entity.Relat params := UpdateRelationshipParams{ ID: pgID, - TrustDomainAConsent: req.TrustDomainAConsent, - TrustDomainBConsent: req.TrustDomainBConsent, + TrustDomainAConsent: ConsentStatus(req.TrustDomainAConsent), + TrustDomainBConsent: ConsentStatus(req.TrustDomainBConsent), } relationship, err := d.querier.UpdateRelationship(ctx, params) diff --git a/pkg/server/datastore/datastore_test.go b/pkg/server/datastore/datastore_test.go index 47a98f220..e9df98c9a 100644 --- a/pkg/server/datastore/datastore_test.go +++ b/pkg/server/datastore/datastore_test.go @@ -239,8 +239,8 @@ func TestUpdateRelationship(t *testing.T) { relationship1 := createRelationship(ctx, t, ds, req1) - relationship1.TrustDomainAConsent = true - relationship1.TrustDomainBConsent = true + relationship1.TrustDomainAConsent = entity.ConsentStatusAccepted + relationship1.TrustDomainBConsent = entity.ConsentStatusDenied updated1, err := ds.CreateOrUpdateRelationship(ctx, relationship1) require.NoError(t, err) @@ -249,8 +249,8 @@ func TestUpdateRelationship(t *testing.T) { stored, err := ds.FindRelationshipByID(ctx, updated1.ID.UUID) require.NoError(t, err) assert.Equal(t, updated1, stored) - assert.True(t, stored.TrustDomainAConsent) - assert.True(t, stored.TrustDomainBConsent) + assert.Equal(t, entity.ConsentStatusAccepted, stored.TrustDomainAConsent) + assert.Equal(t, entity.ConsentStatusDenied, stored.TrustDomainBConsent) } func TestFindRelationshipByTrustDomain(t *testing.T) { diff --git a/pkg/server/datastore/fakedatabase.go b/pkg/server/datastore/fakedatabase.go index 71e12b00b..a8fee06cc 100644 --- a/pkg/server/datastore/fakedatabase.go +++ b/pkg/server/datastore/fakedatabase.go @@ -34,6 +34,38 @@ func NewFakeDB() *FakeDatabase { } } +// WithRelationships overrides all relationships +func (db *FakeDatabase) WithRelationships(relationships ...*entity.Relationship) { + db.mutex.Lock() + defer db.mutex.Unlock() + + db.relationships = make(map[uuid.UUID]*entity.Relationship) + for _, r := range relationships { + db.relationships[r.ID.UUID] = r + } +} + +// WithTrustDomains overrides all trust domains +func (db *FakeDatabase) WithTrustDomains(trustDomains ...*entity.TrustDomain) { + db.mutex.Lock() + defer db.mutex.Unlock() + + db.trustDomains = make(map[uuid.UUID]*entity.TrustDomain) + for _, td := range trustDomains { + db.trustDomains[td.ID.UUID] = td + } +} + +func (db *FakeDatabase) WithBundles(bundles ...*entity.Bundle) { + db.mutex.Lock() + defer db.mutex.Unlock() + + db.bundles = make(map[uuid.UUID]*entity.Bundle) + for _, b := range bundles { + db.bundles[b.ID.UUID] = b + } +} + func (db *FakeDatabase) CreateOrUpdateTrustDomain(ctx context.Context, req *entity.TrustDomain) (*entity.TrustDomain, error) { db.mutex.Lock() defer db.mutex.Unlock() @@ -134,9 +166,12 @@ func (db *FakeDatabase) CreateOrUpdateBundle(ctx context.Context, req *entity.Bu return nil, err } - req.ID = uuid.NullUUID{ - UUID: uuid.New(), - Valid: true, + if !req.ID.Valid { + // it's an insert + req.ID = uuid.NullUUID{ + UUID: uuid.New(), + Valid: true, + } } req.CreatedAt = time.Now() @@ -173,7 +208,7 @@ func (db *FakeDatabase) FindBundleByTrustDomainID(ctx context.Context, trustDoma } for _, bundle := range db.bundles { - if trustDomainID.String() == bundle.TrustDomainID.String() { + if trustDomainID == bundle.TrustDomainID { return bundle, nil } } @@ -383,7 +418,7 @@ func (db *FakeDatabase) FindRelationshipsByTrustDomainID(ctx context.Context, tr return nil, err } - relationships := []*entity.Relationship{} + var relationships []*entity.Relationship for _, r := range db.relationships { matchA := r.TrustDomainAID.String() == trustDomainID.String() matchB := r.TrustDomainBID.String() == trustDomainID.String() @@ -404,7 +439,7 @@ func (db *FakeDatabase) ListRelationships(ctx context.Context) ([]*entity.Relati return nil, err } - relationships := []*entity.Relationship{} + var relationships []*entity.Relationship for _, r := range db.relationships { relationships = append(relationships, r) } diff --git a/pkg/server/datastore/helpers.go b/pkg/server/datastore/helpers.go index ab7d1eabb..03a6ecc9b 100644 --- a/pkg/server/datastore/helpers.go +++ b/pkg/server/datastore/helpers.go @@ -54,8 +54,8 @@ func (r Relationship) ToEntity() (*entity.Relationship, error) { ID: id, TrustDomainAID: r.TrustDomainAID.Bytes, TrustDomainBID: r.TrustDomainBID.Bytes, - TrustDomainAConsent: r.TrustDomainAConsent, - TrustDomainBConsent: r.TrustDomainBConsent, + TrustDomainAConsent: entity.ConsentStatus(r.TrustDomainAConsent), + TrustDomainBConsent: entity.ConsentStatus(r.TrustDomainBConsent), CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt, }, nil diff --git a/pkg/server/datastore/migrations/1_initialize_schema.up.sql b/pkg/server/datastore/migrations/1_initialize_schema.up.sql index 21c368f2a..4a1707f7a 100644 --- a/pkg/server/datastore/migrations/1_initialize_schema.up.sql +++ b/pkg/server/datastore/migrations/1_initialize_schema.up.sql @@ -1,6 +1,6 @@ -- create tables --- CREATE TYPE status AS ENUM ('pending', 'active', 'disabled', 'denied'); +CREATE TYPE consent_status AS ENUM ('accepted', 'denied', 'pending'); CREATE TABLE IF NOT EXISTS trust_domains ( @@ -18,8 +18,8 @@ CREATE TABLE IF NOT EXISTS relationships id UUID PRIMARY KEY DEFAULT gen_random_uuid(), trust_domain_a_id UUID NOT NULL, trust_domain_b_id UUID NOT NULL, - trust_domain_a_consent BOOL NOT NULL DEFAULT FALSE, - trust_domain_b_consent BOOL NOT NULL DEFAULT FALSE, + trust_domain_a_consent consent_status NOT NULL DEFAULT 'pending', + trust_domain_b_consent consent_status NOT NULL DEFAULT 'pending', created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), UNIQUE (trust_domain_a_id, trust_domain_b_id) diff --git a/pkg/server/datastore/models.go b/pkg/server/datastore/models.go index e740b5325..de5a3632a 100644 --- a/pkg/server/datastore/models.go +++ b/pkg/server/datastore/models.go @@ -6,11 +6,56 @@ package datastore import ( "database/sql" + "database/sql/driver" + "fmt" "time" "github.com/jackc/pgtype" ) +type ConsentStatus string + +const ( + ConsentStatusAccepted ConsentStatus = "accepted" + ConsentStatusDenied ConsentStatus = "denied" + ConsentStatusPending ConsentStatus = "pending" +) + +func (e *ConsentStatus) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = ConsentStatus(s) + case string: + *e = ConsentStatus(s) + default: + return fmt.Errorf("unsupported scan type for ConsentStatus: %T", src) + } + return nil +} + +type NullConsentStatus struct { + ConsentStatus ConsentStatus + Valid bool // Valid is true if ConsentStatus is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullConsentStatus) Scan(value interface{}) error { + if value == nil { + ns.ConsentStatus, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.ConsentStatus.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullConsentStatus) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.ConsentStatus), nil +} + type Bundle struct { ID pgtype.UUID TrustDomainID pgtype.UUID @@ -36,8 +81,8 @@ type Relationship struct { ID pgtype.UUID TrustDomainAID pgtype.UUID TrustDomainBID pgtype.UUID - TrustDomainAConsent bool - TrustDomainBConsent bool + TrustDomainAConsent ConsentStatus + TrustDomainBConsent ConsentStatus CreatedAt time.Time UpdatedAt time.Time } diff --git a/pkg/server/datastore/relationships.sql.go b/pkg/server/datastore/relationships.sql.go index e39a69732..be571bb64 100644 --- a/pkg/server/datastore/relationships.sql.go +++ b/pkg/server/datastore/relationships.sql.go @@ -154,8 +154,8 @@ RETURNING id, trust_domain_a_id, trust_domain_b_id, trust_domain_a_consent, trus type UpdateRelationshipParams struct { ID pgtype.UUID - TrustDomainAConsent bool - TrustDomainBConsent bool + TrustDomainAConsent ConsentStatus + TrustDomainBConsent ConsentStatus } func (q *Queries) UpdateRelationship(ctx context.Context, arg UpdateRelationshipParams) (Relationship, error) { diff --git a/pkg/server/endpoints/auth.go b/pkg/server/endpoints/auth.go index 5b36f1013..6b70c40d1 100644 --- a/pkg/server/endpoints/auth.go +++ b/pkg/server/endpoints/auth.go @@ -52,7 +52,7 @@ func (m *AuthenticationMiddleware) Authenticate(bearerToken string, echoCtx echo m.logger.Debugf("Token valid for trust domain: %s\n", tdName) // set the authenticated trust domain ID in the echo context - echoCtx.Set(authTrustDomainKey, td) + echoCtx.Set(authTrustDomainKey, &td) // set the authenticated claims in the echo context echoCtx.Set(authClaimsKey, claims) diff --git a/pkg/server/endpoints/harvester.go b/pkg/server/endpoints/harvester.go index a2d36922d..275146fa2 100644 --- a/pkg/server/endpoints/harvester.go +++ b/pkg/server/endpoints/harvester.go @@ -1,6 +1,9 @@ package endpoints import ( + "context" + "crypto/sha256" + "encoding/base64" "errors" "fmt" "net/http" @@ -15,6 +18,7 @@ import ( "github.com/HewlettPackard/galadriel/pkg/server/api/harvester" "github.com/HewlettPackard/galadriel/pkg/server/datastore" gojwt "github.com/golang-jwt/jwt/v4" + "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" "github.com/spiffe/go-spiffe/v2/spiffeid" @@ -43,13 +47,108 @@ func NewHarvesterAPIHandlers(l logrus.FieldLogger, ds datastore.Datastore, jwtIs } } -// GetRelationships list all the relationships - (GET /relationships) -func (h *HarvesterAPIHandlers) GetRelationships(ctx echo.Context, params harvester.GetRelationshipsParams) error { - return nil +// GetRelationships list all the relationships for a given trust domain name and consent status - (GET /relationships) +// The consent status is optional, if not provided, all relationships will be returned for a given trust domain. If the +// consent status is provided, only relationships with the given consent status for the given trust domain will be returned. +// The trust domain name provided should match the authenticated trust domain. +func (h *HarvesterAPIHandlers) GetRelationships(echoCtx echo.Context, params harvester.GetRelationshipsParams) error { + ctx := echoCtx.Request().Context() + + authTD, err := h.getAuthenticateTrustDomain(echoCtx, params.TrustDomainName) + if err != nil { + return err + } + + consentStatus := *params.ConsentStatus + + switch consentStatus { + case "", api.Accepted, api.Denied, api.Pending: + default: + err := fmt.Errorf("invalid consent status: %q", *params.ConsentStatus) + return h.handleErrorAndLog(err, err.Error(), http.StatusBadRequest) + } + + // get the relationships for the trust domain + relationships, err := h.Datastore.FindRelationshipsByTrustDomainID(ctx, authTD.ID.UUID) + if err != nil { + msg := "error looking up relationships" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) + } + + if consentStatus != "" { + relationships = filterRelationshipsByConsentStatus(authTD.ID.UUID, relationships, consentStatus) + } + + apiRelationships := make([]*api.Relationship, 0, len(relationships)) + for _, r := range relationships { + apiRelationships = append(apiRelationships, api.RelationshipFromEntity(r)) + } + + return chttp.WriteResponse(echoCtx, apiRelationships) } -// PatchRelationshipsRelationshipID accept/denied relationships requests - (PATCH /relationships/{relationshipID}) -func (h *HarvesterAPIHandlers) PatchRelationshipsRelationshipID(ctx echo.Context, relationshipID api.UUID) error { +// PatchRelationship accept/denies relationships requests - (PATCH /relationships/{relationshipID}) +func (h *HarvesterAPIHandlers) PatchRelationship(echoCtx echo.Context, relationshipID api.UUID) error { + ctx := echoCtx.Request().Context() + + authTD, ok := echoCtx.Get(authTrustDomainKey).(*entity.TrustDomain) + if !ok { + err := errors.New("no authenticated trust domain") + return h.handleErrorAndLog(err, err.Error(), http.StatusUnauthorized) + } + + // get the relationships for the trust domain + relationship, err := h.Datastore.FindRelationshipByID(ctx, relationshipID) + if err != nil { + msg := "error looking up relationships" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) + } + + if relationship == nil { + err := fmt.Errorf("relationship not found") + return h.handleErrorAndLog(err, err.Error(), http.StatusNotFound) + } + + if relationship.TrustDomainAID != authTD.ID.UUID && relationship.TrustDomainBID != authTD.ID.UUID { + err := fmt.Errorf("relationship doesn't belong to the authenticated trust domain") + return h.handleErrorAndLog(err, err.Error(), http.StatusUnauthorized) + } + + var patchRequest harvester.PatchRelationship + if err := chttp.FromBody(echoCtx, &patchRequest); err != nil { + msg := "error reading body" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusBadRequest) + } + + consentStatus := patchRequest.ConsentStatus + + switch consentStatus { + case api.Accepted, api.Denied, api.Pending: + default: + err := fmt.Errorf("invalid consent status: %q", consentStatus) + return h.handleErrorAndLog(err, err.Error(), http.StatusBadRequest) + } + + // update the relationship consent status for the authenticated trust domain + if relationship.TrustDomainAID == authTD.ID.UUID { + relationship.TrustDomainAConsent = entity.ConsentStatus(consentStatus) + } else { + relationship.TrustDomainBConsent = entity.ConsentStatus(consentStatus) + } + + if _, err := h.Datastore.CreateOrUpdateRelationship(ctx, relationship); err != nil { + msg := "error updating relationship" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) + } + + if err = chttp.BodylessResponse(echoCtx); err != nil { + return h.handleErrorAndLog(err, err.Error(), http.StatusInternalServerError) + } + return nil } @@ -59,42 +158,44 @@ func (h *HarvesterAPIHandlers) Onboard(echoCtx echo.Context, params harvester.On if params.JoinToken == "" { err := errors.New("join token is required") - return h.handleErrorAndLog(err, err, http.StatusBadRequest) + return h.handleErrorAndLog(err, err.Error(), http.StatusBadRequest) } token, err := h.Datastore.FindJoinToken(ctx, params.JoinToken) if err != nil { - msg := errors.New("error looking up token") - return h.handleErrorAndLog(err, msg, http.StatusBadRequest) + msg := "error looking up token" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) } if token == nil { err := errors.New("token not found") - return h.handleErrorAndLog(err, err, http.StatusBadRequest) + return h.handleErrorAndLog(err, err.Error(), http.StatusBadRequest) } if token.ExpiresAt.Before(time.Now()) { - err := fmt.Errorf("token expired: trust domain ID: %s", token.TrustDomainID) - msg := fmt.Errorf("token expired") - return h.handleErrorAndLog(err, msg, http.StatusBadRequest) + msg := "token expired" + err := fmt.Errorf("%s: trust domain ID: %s", msg, token.TrustDomainID) + return h.handleErrorAndLog(err, msg, http.StatusUnauthorized) } if token.Used { - err := fmt.Errorf("token already used: trust domain ID: %s", token.TrustDomainID) - msg := fmt.Errorf("token already used") + msg := "token already used" + err := fmt.Errorf("%s: trust domain ID: %s", msg, token.TrustDomainID) return h.handleErrorAndLog(err, msg, http.StatusBadRequest) } trustDomain, err := h.Datastore.FindTrustDomainByID(ctx, token.TrustDomainID) if err != nil { - msg := fmt.Errorf("error looking up trust domain") - return h.handleErrorAndLog(err, msg, http.StatusBadRequest) + msg := "error looking up trust domain" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) } // mark token as used - _, err = h.Datastore.UpdateJoinToken(ctx, token.ID.UUID, true) - if err != nil { - msg := fmt.Errorf("internal error") + if _, err := h.Datastore.UpdateJoinToken(ctx, token.ID.UUID, true); err != nil { + msg := "failed to update token" + err := fmt.Errorf("%s: %w", msg, err) return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) } @@ -109,7 +210,8 @@ func (h *HarvesterAPIHandlers) Onboard(echoCtx echo.Context, params harvester.On jwtToken, err := h.jwtIssuer.IssueJWT(ctx, jwtParams) if err != nil { - msg := fmt.Errorf("error generating JWT token") + msg := "error generating JWT token" + err := fmt.Errorf("%s: %w", msg, err) return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) } @@ -122,16 +224,16 @@ func (h *HarvesterAPIHandlers) GetNewJWTToken(echoCtx echo.Context) error { claims, ok := echoCtx.Get(authClaimsKey).(*gojwt.RegisteredClaims) if !ok { - msg := fmt.Errorf("invalid JWT access token") - err := errors.New("error getting claims from context") + msg := "failed to parse JWT access token claims" + err := fmt.Errorf("%s", msg) return h.handleErrorAndLog(err, msg, http.StatusUnauthorized) } sub := claims.Subject subject, err := spiffeid.TrustDomainFromString(sub) if err != nil { - msg := fmt.Errorf("internal error") - err := errors.New("error parsing trust domain from subject") + msg := "failed to parse trust domain from subject" + err := fmt.Errorf("%s: %w", msg, err) return h.handleErrorAndLog(err, msg, http.StatusUnauthorized) } @@ -148,79 +250,213 @@ func (h *HarvesterAPIHandlers) GetNewJWTToken(echoCtx echo.Context) error { newToken, err := h.jwtIssuer.IssueJWT(ctx, ¶ms) if err != nil { - msg := fmt.Errorf("failed to generate new JWT token") + msg := "failed to generate new JWT token" + err := fmt.Errorf("%s: %w", msg, err) return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) } - return echoCtx.JSON(http.StatusOK, newToken) + return chttp.WriteResponse(echoCtx, newToken) } // BundleSync synchronize the status of trust bundles between server and harvester - (POST /trust-domain/{trustDomainName}/bundles/sync) -func (h *HarvesterAPIHandlers) BundleSync(ctx echo.Context, trustDomainName api.TrustDomainName) error { - return nil -} - -// BundlePut uploads a new trust bundle to the server - (PUT /trust-domain/{trustDomainName}/bundles) -func (h *HarvesterAPIHandlers) BundlePut(ctx echo.Context, trustDomainName api.TrustDomainName) error { - h.Logger.Debug("Receiving post bundle request") - gctx := ctx.Request().Context() +func (h *HarvesterAPIHandlers) BundleSync(echoCtx echo.Context, trustDomainName api.TrustDomainName) error { + h.Logger.Debugf("Received bundle sync request from trust domain: %s", trustDomainName) + ctx := echoCtx.Request().Context() - // get the authenticated trust domain from the context - authenticatedTD, ok := ctx.Get(authTrustDomainKey).(*entity.TrustDomain) - if !ok { - err := errors.New("failed to get authenticated trust domain") - return h.handleErrorAndLog(err, err, http.StatusInternalServerError) + authTD, err := h.getAuthenticateTrustDomain(echoCtx, trustDomainName) + if err != nil { + return err } - if authenticatedTD.Name.String() != trustDomainName { - return fmt.Errorf("authenticated trust domain {%s} does not match trust domain in path: {%s}", authenticatedTD.Name, trustDomainName) + // Get the request body + var req harvester.BundleSyncBody + if err := chttp.FromBody(echoCtx, &req); err != nil { + msg := "failed to parse request body" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusBadRequest) } - req := &harvester.BundlePutJSONRequestBody{} - err := chttp.FromBody(ctx, req) + // Look up relationships the authenticated trust domain has with other trust domains + relationships, err := h.Datastore.FindRelationshipsByTrustDomainID(ctx, authTD.ID.UUID) if err != nil { - err := fmt.Errorf("failed to read bundle put body: %v", err) - return h.handleErrorAndLog(err, err, http.StatusBadRequest) + msg := "failed to look up relationships" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) } - if authenticatedTD.Name.String() != req.TrustDomain { - err := fmt.Errorf("authenticated trust domain {%s} does not match trust domain in request body: {%s}", authenticatedTD.Name, req.TrustDomain) - return h.handleErrorAndLog(err, err, http.StatusBadRequest) + // filer out the relationships whose consent status is not "accepted" by the authenticated trust domain + relationships = filterRelationshipsByConsentStatus(authTD.ID.UUID, relationships, api.Accepted) + + resp, err := h.getBundleSyncResult(ctx, authTD, relationships, req) + if err != nil { + msg := "failed to generate bundle sync result" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) } - storedBundle, err := h.Datastore.FindBundleByTrustDomainID(gctx, authenticatedTD.ID.UUID) + return chttp.WriteResponse(echoCtx, resp) +} + +// BundlePut uploads a new trust bundle to the server - (PUT /trust-domain/{trustDomainName}/bundles) +func (h *HarvesterAPIHandlers) BundlePut(echoCtx echo.Context, trustDomainName api.TrustDomainName) error { + h.Logger.Infof("Received post bundle request from trust domain: %s", trustDomainName) + ctx := echoCtx.Request().Context() + + authTD, err := h.getAuthenticateTrustDomain(echoCtx, trustDomainName) if err != nil { - return h.handleErrorAndLog(err, err, http.StatusInternalServerError) + return err } - if req.TrustBundle == "" { - return nil + req := &harvester.BundlePutJSONRequestBody{} + if err := chttp.FromBody(echoCtx, req); err != nil { + msg := "failed to read bundle from request body" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusBadRequest) + } + + if err := validateBundleRequest(req); err != nil { + err := fmt.Errorf("invalid bundle request: %v", err) + return h.handleErrorAndLog(err, err.Error(), http.StatusBadRequest) + } + + if authTD.Name.String() != req.TrustDomain { + err := fmt.Errorf("trust domain in request bundle %q does not match authenticated trust domain: %q", req.TrustDomain, authTD.Name.String()) + return h.handleErrorAndLog(err, err.Error(), http.StatusUnauthorized) } bundle, err := req.ToEntity() if err != nil { - err := fmt.Errorf("failed to convert bundle put body to entity: %v", err) - return h.handleErrorAndLog(err, err, http.StatusInternalServerError) + msg := "failed to parse request bundle" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusBadRequest) + } + // ensure that the bundle's trust domain ID matches the authenticated trust domain ID + bundle.TrustDomainID = authTD.ID.UUID + + storedBundle, err := h.Datastore.FindBundleByTrustDomainID(ctx, authTD.ID.UUID) + if err != nil { + msg := "failed looking up bundle in DB" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) } + // the bundle already exists in the datastore, so we need to update it if storedBundle != nil { - bundle.TrustDomainID = storedBundle.TrustDomainID + bundle.ID = storedBundle.ID } - _, err = h.Datastore.CreateOrUpdateBundle(gctx, bundle) - if err != nil { - return h.handleErrorAndLog(err, err, http.StatusInternalServerError) + if _, err := h.Datastore.CreateOrUpdateBundle(ctx, bundle); err != nil { + msg := "failed to store bundle in DB" + err := fmt.Errorf("%s: %w", msg, err) + return h.handleErrorAndLog(err, msg, http.StatusInternalServerError) + } + + if err = chttp.BodylessResponse(echoCtx); err != nil { + return h.handleErrorAndLog(err, err.Error(), http.StatusInternalServerError) + } + + return nil +} + +func (h *HarvesterAPIHandlers) getBundleSyncResult(ctx context.Context, authTD *entity.TrustDomain, relationships []*entity.Relationship, req harvester.BundleSyncBody) (*harvester.BundleSyncResult, error) { + resp := &harvester.BundleSyncResult{ + State: make(map[string]api.BundleDigest, len(relationships)), + Updates: make(harvester.TrustBundleSync), + } + + for _, relationship := range relationships { + otherID := relationship.TrustDomainAID + if relationship.TrustDomainAID == authTD.ID.UUID { + otherID = relationship.TrustDomainBID + } + bundle, err := h.Datastore.FindBundleByTrustDomainID(ctx, otherID) + if err != nil { + return nil, err + } + + // Calculate the sha256 digest of the stored bundle + digest := sha256.Sum256(bundle.Data) + strDigest := encodeToBase64(digest[:]) + + // Look up the bundle digest in the request + reqDigest, ok := req.State[bundle.TrustDomainName.String()] + if !ok || strDigest != reqDigest { + // The bundle digest in the request is different from the stored one, so the bundle needs to be updated + updateItem := harvester.TrustBundleSyncItem{} + updateItem.TrustBundle = encodeToBase64(bundle.Data) + updateItem.Signature = encodeToBase64(bundle.Signature) + resp.Updates[bundle.TrustDomainName.String()] = updateItem + } + + // Add the bundle to the current state + resp.State[bundle.TrustDomainName.String()] = encodeToBase64(digest[:]) + } + + return resp, nil +} + +// handleErrorAndLog logs the error and returns an HTTP error with a unique error ID which can be used to trace the error +func (h *HarvesterAPIHandlers) handleErrorAndLog(logErr error, message string, code int) error { + errID := uuid.NewString() + logMsg := fmt.Sprintf("%v (error ID: %s)", logErr, errID) + logMsg = util.LogSanitize(logMsg) + h.Logger.Errorf(logMsg) + + errMsg := fmt.Sprintf("%s (error ID: %s)", message, errID) + return echo.NewHTTPError(code, errMsg) +} + +func filterRelationshipsByConsentStatus(trustDomain uuid.UUID, relationships []*entity.Relationship, status api.ConsentStatus) []*entity.Relationship { + filtered := make([]*entity.Relationship, 0) + + for _, relationship := range relationships { + trustDomainA := relationship.TrustDomainAID + trustDomainB := relationship.TrustDomainBID + trustDomainAConsent := api.ConsentStatus(relationship.TrustDomainAConsent) + trustDomainBConsent := api.ConsentStatus(relationship.TrustDomainBConsent) + + isConsentStatusMatch := (trustDomainA == trustDomain && trustDomainAConsent == status) || + (trustDomainB == trustDomain && trustDomainBConsent == status) + + if isConsentStatusMatch { + filtered = append(filtered, relationship) + } + } + + return filtered +} + +func (h *HarvesterAPIHandlers) getAuthenticateTrustDomain(echoCtx echo.Context, trustDomainName string) (*entity.TrustDomain, error) { + authTD, ok := echoCtx.Get(authTrustDomainKey).(*entity.TrustDomain) + if !ok { + err := errors.New("no authenticated trust domain") + return nil, h.handleErrorAndLog(err, err.Error(), http.StatusUnauthorized) + } + + if authTD.Name.String() != trustDomainName { + err := fmt.Errorf("request trust domain %q does not match authenticated trust domain %q", trustDomainName, authTD.Name.String()) + return nil, h.handleErrorAndLog(err, err.Error(), http.StatusUnauthorized) + } + + return authTD, nil +} + +func validateBundleRequest(req *harvester.BundlePutJSONRequestBody) error { + if req.TrustDomain == "" { + return errors.New("bundle trust domain is required") + } + + if req.TrustBundle == "" { + return errors.New("trust bundle is required") } - if err = chttp.BodylessResponse(ctx); err != nil { - return h.handleErrorAndLog(err, err, http.StatusInternalServerError) + if req.Signature == "" { + return errors.New("bundle signature is required") } return nil } -func (h *HarvesterAPIHandlers) handleErrorAndLog(logErr, msg error, code int) error { - errMsg := util.LogSanitize(logErr.Error()) - h.Logger.Errorf(errMsg) - return echo.NewHTTPError(code, msg.Error()) +func encodeToBase64(data []byte) string { + return base64.StdEncoding.EncodeToString(data) } diff --git a/pkg/server/endpoints/harvester_test.go b/pkg/server/endpoints/harvester_test.go index c3e1218ee..06dba651b 100644 --- a/pkg/server/endpoints/harvester_test.go +++ b/pkg/server/endpoints/harvester_test.go @@ -2,12 +2,16 @@ package endpoints import ( "context" + "crypto/sha256" + "encoding/base64" "encoding/json" "net/http" "net/http/httptest" + "reflect" "strings" "testing" + "github.com/HewlettPackard/galadriel/pkg/common/api" "github.com/HewlettPackard/galadriel/pkg/common/entity" "github.com/HewlettPackard/galadriel/pkg/server/api/harvester" "github.com/HewlettPackard/galadriel/pkg/server/datastore" @@ -23,13 +27,31 @@ import ( ) const ( - jwtPath = "/jwt" - onboardPath = "/onboard" + jwtPath = "/jwt" + onboardPath = "/onboard" + relationshipsPath = "/relationships" +) + +var ( + tdA = &entity.TrustDomain{Name: spiffeid.RequireTrustDomainFromString("td-a.org"), ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + tdB = &entity.TrustDomain{Name: spiffeid.RequireTrustDomainFromString("td-b.org"), ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + tdC = &entity.TrustDomain{Name: spiffeid.RequireTrustDomainFromString("td-c.org"), ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + pendingRelAB = &entity.Relationship{TrustDomainAID: tdA.ID.UUID, TrustDomainBID: tdB.ID.UUID, TrustDomainAConsent: entity.ConsentStatusPending, TrustDomainBConsent: entity.ConsentStatusPending, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + pendingRelAC = &entity.Relationship{TrustDomainAID: tdA.ID.UUID, TrustDomainBID: tdC.ID.UUID, TrustDomainAConsent: entity.ConsentStatusPending, TrustDomainBConsent: entity.ConsentStatusPending, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + acceptedPendingRelAB = &entity.Relationship{TrustDomainAID: tdA.ID.UUID, TrustDomainBID: tdB.ID.UUID, TrustDomainAConsent: entity.ConsentStatusAccepted, TrustDomainBConsent: entity.ConsentStatusPending, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + deniedAcceptedRelAB = &entity.Relationship{TrustDomainAID: tdA.ID.UUID, TrustDomainBID: tdB.ID.UUID, TrustDomainAConsent: entity.ConsentStatusDenied, TrustDomainBConsent: entity.ConsentStatusAccepted, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + acceptedDeniedRelAC = &entity.Relationship{TrustDomainAID: tdA.ID.UUID, TrustDomainBID: tdC.ID.UUID, TrustDomainAConsent: entity.ConsentStatusAccepted, TrustDomainBConsent: entity.ConsentStatusDenied, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + acceptedAcceptedRelBC = &entity.Relationship{TrustDomainAID: tdB.ID.UUID, TrustDomainBID: tdC.ID.UUID, TrustDomainAConsent: entity.ConsentStatusAccepted, TrustDomainBConsent: entity.ConsentStatusAccepted, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + + bundleA = &entity.Bundle{Data: []byte("bundle-A"), Signature: []byte("signature-A"), TrustDomainName: tdA.Name, TrustDomainID: tdA.ID.UUID, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + bundleB = &entity.Bundle{Data: []byte("bundle-B"), Signature: []byte("signature-B"), TrustDomainName: tdB.Name, TrustDomainID: tdB.ID.UUID, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} + bundleC = &entity.Bundle{Data: []byte("bundle-C"), Signature: []byte("signature-C"), TrustDomainName: tdC.Name, TrustDomainID: tdC.ID.UUID, ID: uuid.NullUUID{UUID: uuid.New(), Valid: true}} ) type HarvesterTestSetup struct { EchoCtx echo.Context Handler *HarvesterAPIHandlers + Datastore *datastore.FakeDatabase JWTIssuer *fakejwtissuer.JWTIssuer Recorder *httptest.ResponseRecorder } @@ -51,6 +73,7 @@ func NewHarvesterTestSetup(t *testing.T, method, url, body string) *HarvesterTes Recorder: rec, Handler: NewHarvesterAPIHandlers(logger, fakeDB, jwtIssuer, jwtValidator), JWTIssuer: jwtIssuer, + Datastore: fakeDB, } } @@ -69,6 +92,19 @@ func SetupTrustDomain(t *testing.T, ds datastore.Datastore) *entity.TrustDomain return trustDomain } +func SetupBundle(t *testing.T, ds datastore.Datastore, td uuid.UUID) *entity.Bundle { + bundle := &entity.Bundle{ + TrustDomainID: td, + Data: []byte("test-bundle"), + Signature: []byte("test-signature"), + } + + _, err := ds.CreateOrUpdateBundle(context.TODO(), bundle) + require.NoError(t, err) + + return bundle +} + func SetupJoinToken(t *testing.T, ds datastore.Datastore, td uuid.UUID) *entity.JoinToken { jt := &entity.JoinToken{ Token: "test-join-token", @@ -82,11 +118,181 @@ func SetupJoinToken(t *testing.T, ds datastore.Datastore, td uuid.UUID) *entity. } func TestTCPGetRelationships(t *testing.T) { - t.Skip("Missing tests will be added when the API be implemented") + t.Run("Successfully get accepted relationships", func(t *testing.T) { + testGetRelationships(t, func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain) { + setup.EchoCtx.Set(authTrustDomainKey, trustDomain) + }, api.Accepted, tdA, 2) + }) + + t.Run("Successfully get denied relationships", func(t *testing.T) { + testGetRelationships(t, func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain) { + setup.EchoCtx.Set(authTrustDomainKey, trustDomain) + }, api.Denied, tdC, 1) + }) + + t.Run("Successfully get pending relationships", func(t *testing.T) { + testGetRelationships(t, func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain) { + setup.EchoCtx.Set(authTrustDomainKey, trustDomain) + }, api.Pending, tdB, 2) + }) + + t.Run("Successfully get all relationships", func(t *testing.T) { + testGetRelationships(t, func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain) { + setup.EchoCtx.Set(authTrustDomainKey, trustDomain) + }, "", tdA, 4) + }) + + t.Run("Fails with invalid consent status", func(t *testing.T) { + setup := NewHarvesterTestSetup(t, http.MethodGet, relationshipsPath, "") + echoCtx := setup.EchoCtx + setup.EchoCtx.Set(authTrustDomainKey, tdA) + + tdName := tdA.Name.String() + status := api.ConsentStatus("invalid") + params := harvester.GetRelationshipsParams{ + TrustDomainName: tdName, + ConsentStatus: &status, + } + + err := setup.Handler.GetRelationships(echoCtx, params) + assert.Error(t, err) + assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code) + assert.Contains(t, err.(*echo.HTTPError).Message, "invalid consent status: \"invalid\"") + }) + + t.Run("Fails if no authenticated trust domain", func(t *testing.T) { + setup := NewHarvesterTestSetup(t, http.MethodGet, relationshipsPath, "") + echoCtx := setup.EchoCtx + + tdName := tdA.Name.String() + params := harvester.GetRelationshipsParams{ + TrustDomainName: tdName, + } + + err := setup.Handler.GetRelationships(echoCtx, params) + assert.Error(t, err) + assert.Equal(t, http.StatusUnauthorized, err.(*echo.HTTPError).Code) + assert.Contains(t, err.(*echo.HTTPError).Message, "no authenticated trust domain") + }) + + t.Run("Fails if authenticated trust domain does not match trust domain parameter", func(t *testing.T) { + setup := NewHarvesterTestSetup(t, http.MethodGet, relationshipsPath, "") + echoCtx := setup.EchoCtx + setup.EchoCtx.Set(authTrustDomainKey, tdA) + + tdName := tdB.Name.String() + params := harvester.GetRelationshipsParams{ + TrustDomainName: tdName, + } + + err := setup.Handler.GetRelationships(echoCtx, params) + assert.Error(t, err) + assert.Equal(t, http.StatusUnauthorized, err.(*echo.HTTPError).Code) + assert.Contains(t, err.(*echo.HTTPError).Message, "request trust domain \"td-b.org\" does not match authenticated trust domain \"td-a.org\"") + }) +} + +func testGetRelationships(t *testing.T, setupFn func(*HarvesterTestSetup, *entity.TrustDomain), status api.ConsentStatus, trustDomain *entity.TrustDomain, expectedRelationshipCount int) { + setup := NewHarvesterTestSetup(t, http.MethodGet, relationshipsPath, "") + echoCtx := setup.EchoCtx + + setup.Datastore.WithTrustDomains(tdA, tdB, tdC) + setup.Datastore.WithRelationships(pendingRelAB, pendingRelAC, acceptedPendingRelAB, acceptedDeniedRelAC, acceptedAcceptedRelBC) + + setupFn(setup, trustDomain) + + tdName := trustDomain.Name.String() + params := harvester.GetRelationshipsParams{ + TrustDomainName: tdName, + ConsentStatus: &status, + } + + err := setup.Handler.GetRelationships(echoCtx, params) + assert.NoError(t, err) + + recorder := setup.Recorder + assert.Equal(t, http.StatusOK, recorder.Code) + assert.NotEmpty(t, recorder.Body) + + var relationships []*api.Relationship + err = json.Unmarshal(recorder.Body.Bytes(), &relationships) + assert.NoError(t, err) + assert.Len(t, relationships, expectedRelationshipCount) + + if status == "" { + return // no need to assert consent status + } + // assert that all relationships have the expected consent status for the specified trust domain + for _, rel := range relationships { + if rel.TrustDomainAId == trustDomain.ID.UUID { + assert.Equal(t, status, rel.TrustDomainAConsent) + } + if rel.TrustDomainBId == trustDomain.ID.UUID { + assert.Equal(t, status, rel.TrustDomainBConsent) + } + } } func TestTCPPatchRelationshipRelationshipID(t *testing.T) { - t.Skip("Missing tests will be added when the API be implemented") + t.Run("Successfully patch pending relationship to accepted", func(t *testing.T) { + testPatchRelationship(t, func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain) { + setup.EchoCtx.Set(authTrustDomainKey, tdA) + }, tdA, pendingRelAC, api.Accepted) + }) + t.Run("Successfully patch pending relationship to denied", func(t *testing.T) { + testPatchRelationship(t, func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain) { + setup.EchoCtx.Set(authTrustDomainKey, tdA) + }, tdA, pendingRelAC, api.Denied) + }) + t.Run("Successfully patch pending relationship to accepted with other trust domain", func(t *testing.T) { + testPatchRelationship(t, func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain) { + setup.EchoCtx.Set(authTrustDomainKey, tdC) + }, tdC, pendingRelAC, api.Accepted) + }) + t.Run("Successfully patch accepted relationship to denied", func(t *testing.T) { + testPatchRelationship(t, func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain) { + setup.EchoCtx.Set(authTrustDomainKey, tdB) + }, tdB, acceptedAcceptedRelBC, api.Denied) + }) +} + +func testPatchRelationship(t *testing.T, f func(setup *HarvesterTestSetup, trustDomain *entity.TrustDomain), trustDomain *entity.TrustDomain, relationship *entity.Relationship, status api.ConsentStatus) { + requestBody := &harvester.PatchRelationship{ + ConsentStatus: status, + } + + body, err := json.Marshal(requestBody) + require.NoError(t, err) + + setup := NewHarvesterTestSetup(t, http.MethodPatch, relationshipsPath+"/"+relationship.ID.UUID.String(), string(body)) + echoCtx := setup.EchoCtx + + setup.Datastore.WithTrustDomains(tdA, tdB, tdC) + setup.Datastore.WithRelationships(relationship) + + f(setup, trustDomain) + + err = setup.Handler.PatchRelationship(echoCtx, relationship.ID.UUID) + assert.NoError(t, err) + + recorder := setup.Recorder + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Empty(t, recorder.Body) + assert.Equal(t, status, status) + + // lookup relationship to assert that it was updated + rel, err := setup.Datastore.FindRelationshipByID(context.Background(), relationship.ID.UUID) + assert.NoError(t, err) + + if rel.TrustDomainAID == trustDomain.ID.UUID { + assert.Equal(t, entity.ConsentStatus(status), rel.TrustDomainAConsent) + // the other trust domain's consent status should not have changed + assert.Equal(t, relationship.TrustDomainBConsent, rel.TrustDomainBConsent) + } else { + assert.Equal(t, entity.ConsentStatus(status), rel.TrustDomainBConsent) + // the other trust domain's consent status should not have changed + assert.Equal(t, relationship.TrustDomainAConsent, rel.TrustDomainAConsent) + } } func TestTCPOnboard(t *testing.T) { @@ -125,7 +331,7 @@ func TestTCPOnboard(t *testing.T) { httpErr := err.(*echo.HTTPError) assert.Equal(t, http.StatusBadRequest, httpErr.Code) - assert.Equal(t, "join token is required", httpErr.Message) + assert.Contains(t, httpErr.Message, "join token is required") }) t.Run("Onboard with join token that does not exist fails", func(t *testing.T) { harvesterTestSetup := NewHarvesterTestSetup(t, http.MethodGet, onboardPath, "") @@ -142,7 +348,7 @@ func TestTCPOnboard(t *testing.T) { httpErr := err.(*echo.HTTPError) assert.Equal(t, http.StatusBadRequest, httpErr.Code) - assert.Equal(t, "token not found", httpErr.Message) + assert.Contains(t, httpErr.Message, "token not found") }) t.Run("Onboard with join token that was used", func(t *testing.T) { harvesterTestSetup := NewHarvesterTestSetup(t, http.MethodGet, onboardPath, "") @@ -163,7 +369,7 @@ func TestTCPOnboard(t *testing.T) { httpErr := err.(*echo.HTTPError) assert.Equal(t, http.StatusBadRequest, httpErr.Code) - assert.Equal(t, "token already used", httpErr.Message) + assert.Contains(t, httpErr.Message, "token already used") }) } @@ -200,41 +406,259 @@ func TestTCPGetNewJWTToken(t *testing.T) { require.Error(t, err) assert.Equal(t, http.StatusUnauthorized, err.(*echo.HTTPError).Code) - assert.Equal(t, "invalid JWT access token", err.(*echo.HTTPError).Message) + assert.Contains(t, err.(*echo.HTTPError).Message, "failed to parse JWT access token claims") }) } func TestTCPBundleSync(t *testing.T) { - t.Skip("Missing tests will be added when the API be implemented") + testCases := []struct { + name string + relationships []*entity.Relationship + bundleState harvester.BundleSyncBody + expected harvester.BundleSyncResult + }{ + { + name: "Successfully sync no new bundles", + relationships: []*entity.Relationship{acceptedPendingRelAB, acceptedDeniedRelAC, acceptedAcceptedRelBC}, + bundleState: harvester.BundleSyncBody{ + State: map[string]api.BundleDigest{ + tdB.Name.String(): base64EncodedDigest(bundleB.Data), + tdC.Name.String(): base64EncodedDigest(bundleC.Data), + }, + }, + expected: harvester.BundleSyncResult{ + State: harvester.BundlesDigests{ + tdB.Name.String(): base64EncodedDigest(bundleB.Data), + tdC.Name.String(): base64EncodedDigest(bundleC.Data), + }, + Updates: harvester.TrustBundleSync{}, + }, + }, + { + name: "Successfully sync one new bundle for one accepted relationship", + relationships: []*entity.Relationship{acceptedPendingRelAB, acceptedDeniedRelAC, acceptedAcceptedRelBC}, + bundleState: harvester.BundleSyncBody{ + State: map[string]api.BundleDigest{ + tdC.Name.String(): base64EncodedDigest(bundleC.Data), + }, + }, + expected: harvester.BundleSyncResult{ + State: harvester.BundlesDigests{ + tdB.Name.String(): base64EncodedDigest(bundleB.Data), + tdC.Name.String(): base64EncodedDigest(bundleC.Data), + }, + Updates: harvester.TrustBundleSync{ + tdB.Name.String(): harvester.TrustBundleSyncItem{ + TrustBundle: encodeToBase64(bundleB.Data), + Signature: encodeToBase64(bundleB.Signature), + }, + }, + }, + }, + { + name: "Successfully sync two new bundles for two accepted relationships", + relationships: []*entity.Relationship{acceptedPendingRelAB, acceptedDeniedRelAC, acceptedAcceptedRelBC}, + bundleState: harvester.BundleSyncBody{ + State: map[string]api.BundleDigest{}, + }, + expected: harvester.BundleSyncResult{ + State: harvester.BundlesDigests{ + tdB.Name.String(): base64EncodedDigest(bundleB.Data), + tdC.Name.String(): base64EncodedDigest(bundleC.Data), + }, + Updates: harvester.TrustBundleSync{ + tdB.Name.String(): harvester.TrustBundleSyncItem{ + TrustBundle: encodeToBase64(bundleB.Data), + Signature: encodeToBase64(bundleB.Signature), + }, + tdC.Name.String(): harvester.TrustBundleSyncItem{ + TrustBundle: encodeToBase64(bundleC.Data), + Signature: encodeToBase64(bundleC.Signature), + }, + }, + }, + }, + { + name: "Successfully sync one new bundle for one accepted relationship, not including the pending relationship", + relationships: []*entity.Relationship{acceptedPendingRelAB, pendingRelAC, acceptedAcceptedRelBC}, + bundleState: harvester.BundleSyncBody{ + State: map[string]api.BundleDigest{}, + }, + expected: harvester.BundleSyncResult{ + State: harvester.BundlesDigests{ + tdB.Name.String(): base64EncodedDigest(bundleB.Data), + }, + Updates: harvester.TrustBundleSync{ + tdB.Name.String(): harvester.TrustBundleSyncItem{ + TrustBundle: encodeToBase64(bundleB.Data), + Signature: encodeToBase64(bundleB.Signature), + }, + }, + }, + }, + { + name: "Successfully sync one new bundle for one accepted relationship, not including the denied relationship", + relationships: []*entity.Relationship{acceptedDeniedRelAC, deniedAcceptedRelAB, acceptedAcceptedRelBC}, + bundleState: harvester.BundleSyncBody{ + State: map[string]api.BundleDigest{}, + }, + expected: harvester.BundleSyncResult{ + State: harvester.BundlesDigests{ + tdC.Name.String(): base64EncodedDigest(bundleC.Data), + }, + Updates: harvester.TrustBundleSync{ + tdC.Name.String(): harvester.TrustBundleSyncItem{ + TrustBundle: encodeToBase64(bundleC.Data), + Signature: encodeToBase64(bundleC.Signature), + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + body, err := json.Marshal(tc.bundleState) + require.NoError(t, err) + + setup := NewHarvesterTestSetup(t, http.MethodPost, "/trust-domain/:trustDomainName/bundles/sync", string(body)) + echoCtx := setup.EchoCtx + setup.EchoCtx.Set(authTrustDomainKey, tdA) + + setup.Datastore.WithTrustDomains(tdA, tdB, tdC) + setup.Datastore.WithRelationships(tc.relationships...) + setup.Datastore.WithBundles(bundleA, bundleB, bundleC) + + // test bundle sync + err = setup.Handler.BundleSync(echoCtx, tdA.Name.String()) + assert.NoError(t, err) + + recorder := setup.Recorder + assert.Equal(t, http.StatusOK, recorder.Code) + assert.NoError(t, err) + + var bundles harvester.BundleSyncResult + err = json.Unmarshal(recorder.Body.Bytes(), &bundles) + require.NoError(t, err) + + assert.Equal(t, tc.expected, bundles) + }) + } } -func TestTCPBundlePut(t *testing.T) { - t.Run("Succesfully register bundles for a trust domain", func(t *testing.T) { +func TestBundlePut(t *testing.T) { + t.Run("Successfully post new bundle for a trust domain", func(t *testing.T) { + setupFunc := func(setup *HarvesterTestSetup) *entity.TrustDomain { + td := SetupTrustDomain(t, setup.Handler.Datastore) + setup.EchoCtx.Set(authTrustDomainKey, td) + return td + } + testBundlePut(t, setupFunc, http.StatusOK, "") + }) + + t.Run("Successfully post bundle update for a trust domain", func(t *testing.T) { + setupFunc := func(setup *HarvesterTestSetup) *entity.TrustDomain { + td := SetupTrustDomain(t, setup.Handler.Datastore) + setup.EchoCtx.Set(authTrustDomainKey, td) + SetupBundle(t, setup.Handler.Datastore, td.ID.UUID) + return td + } + testBundlePut(t, setupFunc, http.StatusOK, "") + }) + + t.Run("Fail post bundle no authenticated trust domain", func(t *testing.T) { bundlePut := harvester.BundlePut{ - Signature: "", - SigningCertificate: "", + Signature: "bundle signature", + SigningCertificate: "certificate PEM", TrustBundle: "a new bundle", TrustDomain: testTrustDomain, } body, err := json.Marshal(bundlePut) - assert.NoError(t, err) + require.NoError(t, err) - harvesterTestSetup := NewHarvesterTestSetup(t, http.MethodPut, "/trust-domain/:trustDomainName/bundles", string(body)) - echoCtx := harvesterTestSetup.EchoCtx + setup := NewHarvesterTestSetup(t, http.MethodPut, "/trust-domain/:trustDomainName/bundles", string(body)) + setup.EchoCtx.Set(authTrustDomainKey, "") - // Creating Trust Domain - td := SetupTrustDomain(t, harvesterTestSetup.Handler.Datastore) + err = setup.Handler.BundlePut(setup.EchoCtx, testTrustDomain) + require.Error(t, err) + assert.Equal(t, http.StatusUnauthorized, err.(*echo.HTTPError).Code) + assert.Contains(t, err.(*echo.HTTPError).Message, "no authenticated trust domain") + }) - assert.NoError(t, err) - echoCtx.Set(authTrustDomainKey, td) + t.Run("Fail post bundle missing Trust bundle", func(t *testing.T) { + testInvalidBundleRequest(t, "TrustBundle", "", http.StatusBadRequest, "invalid bundle request: trust bundle is required") + }) - // Test Main Objective - err = harvesterTestSetup.Handler.BundlePut(echoCtx, testTrustDomain) - assert.NoError(t, err) + t.Run("Fail post bundle missing bundle signature", func(t *testing.T) { + testInvalidBundleRequest(t, "Signature", "", http.StatusBadRequest, "invalid bundle request: bundle signature is required") + }) - recorder := harvesterTestSetup.Recorder - assert.Equal(t, http.StatusOK, recorder.Code) - assert.Empty(t, recorder.Body) + t.Run("Fail post bundle missing bundle trust domain", func(t *testing.T) { + testInvalidBundleRequest(t, "TrustDomain", "", http.StatusBadRequest, "invalid bundle request: bundle trust domain is required") + }) + + t.Run("Fail post bundle bundle trust domain does not match authenticated trust domain", func(t *testing.T) { + testInvalidBundleRequest(t, "TrustDomain", "other-trust-domain", http.StatusUnauthorized, "trust domain in request bundle \"other-trust-domain\" does not match authenticated trust domain: \"test.com\"") }) } + +func testBundlePut(t *testing.T, setupFunc func(*HarvesterTestSetup) *entity.TrustDomain, expectedStatusCode int, expectedResponseBody string) { + bundlePut := harvester.BundlePut{ + Signature: "bundle signature", + SigningCertificate: "certificate PEM", + TrustBundle: "a new bundle", + TrustDomain: testTrustDomain, + } + + body, err := json.Marshal(bundlePut) + require.NoError(t, err) + + setup := NewHarvesterTestSetup(t, http.MethodPut, "/trust-domain/:trustDomainName/bundles", string(body)) + echoCtx := setup.EchoCtx + + td := setupFunc(setup) + + err = setup.Handler.BundlePut(echoCtx, testTrustDomain) + require.NoError(t, err) + + recorder := setup.Recorder + assert.Equal(t, expectedStatusCode, recorder.Code) + assert.Equal(t, expectedResponseBody, recorder.Body.String()) + + storedBundle, err := setup.Handler.Datastore.FindBundleByTrustDomainID(context.Background(), td.ID.UUID) + require.NoError(t, err) + assert.Equal(t, bundlePut.Signature, string(storedBundle.Signature)) + assert.Equal(t, bundlePut.SigningCertificate, string(storedBundle.SigningCertificate)) + assert.Equal(t, bundlePut.TrustBundle, string(storedBundle.Data)) + assert.Equal(t, td.ID.UUID, storedBundle.TrustDomainID) +} + +func testInvalidBundleRequest(t *testing.T, fieldName string, fieldValue interface{}, expectedStatusCode int, expectedErrorMessage string) { + bundlePut := harvester.BundlePut{ + Signature: "test-signature", + SigningCertificate: "certificate PEM", + TrustBundle: "test trust bundle", + TrustDomain: testTrustDomain, + } + reflect.ValueOf(&bundlePut).Elem().FieldByName(fieldName).Set(reflect.ValueOf(fieldValue)) + + body, err := json.Marshal(bundlePut) + require.NoError(t, err) + + setup := NewHarvesterTestSetup(t, http.MethodPut, "/trust-domain/:trustDomainName/bundles", string(body)) + echoCtx := setup.EchoCtx + + td := SetupTrustDomain(t, setup.Handler.Datastore) + echoCtx.Set(authTrustDomainKey, td) + + err = setup.Handler.BundlePut(echoCtx, testTrustDomain) + require.Error(t, err) + assert.Equal(t, expectedStatusCode, err.(*echo.HTTPError).Code) + assert.Contains(t, err.(*echo.HTTPError).Message, expectedErrorMessage) +} + +func base64EncodedDigest(data []byte) string { + hash := sha256.Sum256(data) + return base64.StdEncoding.EncodeToString(hash[:]) +}