diff --git a/build/build_info.go b/build/build_info.go deleted file mode 100644 index 21c85ec48..000000000 --- a/build/build_info.go +++ /dev/null @@ -1,39 +0,0 @@ -package build - -var defaultBuildInfo = defaultInfo{} -var info Info = defaultBuildInfo - -func GetBuildInfo() Info { - return info -} - -func InitBuildInfo(buildInfo Info) { - if info == defaultBuildInfo { - info = buildInfo - } -} - -type Info interface { - Version() string - Revision() string - BuildDate() string - Branch() string -} - -type defaultInfo struct{} - -func (d defaultInfo) Version() string { - return "unknown" -} - -func (d defaultInfo) Revision() string { - return "unknown" -} - -func (d defaultInfo) BuildDate() string { - return "unknown" -} - -func (d defaultInfo) Branch() string { - return "unknown" -} diff --git a/controller/apierror/api.go b/controller/apierror/api.go deleted file mode 100644 index 3563f82ad..000000000 --- a/controller/apierror/api.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright NetFoundry, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package apierror - -import ( - "fmt" - "github.com/Jeffail/gabs" - "github.com/go-openapi/errors" - "github.com/openziti/edge/controller/schema" - "github.com/openziti/edge/rest_model" - "github.com/openziti/foundation/util/errorz" -) - -func ToRestModel(e *errorz.ApiError, requestId string) *rest_model.APIError { - ret := &rest_model.APIError{ - Args: nil, - Code: e.Code, - Message: e.Message, - RequestID: requestId, - } - - if e.Cause != nil { - - //unwrap first error in composite error - compositeErr, ok := e.Cause.(*errors.CompositeError) - for ok { - e.Cause = compositeErr.Errors[0] - compositeErr, ok = e.Cause.(*errors.CompositeError) - } - - if causeApiError, ok := e.Cause.(*errorz.ApiError); ok { - //standard apierror - ret.Cause = &rest_model.APIErrorCause{ - APIError: *ToRestModel(causeApiError, requestId), - } - } else if causeJsonSchemaError, ok := e.Cause.(*schema.ValidationErrors); ok { - //only possible from config type JSON schema validation - ret.Cause = &rest_model.APIErrorCause{ - APIFieldError: rest_model.APIFieldError{ - Field: causeJsonSchemaError.Errors[0].Field, - Reason: causeJsonSchemaError.Errors[0].Error(), - Value: fmt.Sprintf("%v", causeJsonSchemaError.Errors[0].Value), - }, - } - } else if causeFieldErr, ok := e.Cause.(*errorz.FieldError); ok { - //authenticator modules and enrollment only - //todo: see if we can remove this by not using FieldError - ret.Cause = &rest_model.APIErrorCause{ - APIFieldError: rest_model.APIFieldError{ - Field: causeFieldErr.FieldName, - Value: fmt.Sprintf("%v", causeFieldErr.FieldValue), - Reason: causeFieldErr.Reason, - }, - } - if ret.Code == errorz.InvalidFieldCode { - ret.Code = errorz.CouldNotValidateCode - ret.Message = errorz.CouldNotValidateMessage - } - - } else if causeFieldErr, ok := e.Cause.(*errors.Validation); ok { - //open api validation errors - ret.Cause = &rest_model.APIErrorCause{ - APIFieldError: rest_model.APIFieldError{ - Field: causeFieldErr.Name, - Reason: causeFieldErr.Error(), - Value: fmt.Sprintf("%v", causeFieldErr.Value), - }, - } - ret.Code = errorz.CouldNotValidateCode - ret.Message = errorz.CouldNotValidateMessage - - } else if genericErr, ok := e.Cause.(GenericCauseError); ok { - ret.Cause = &rest_model.APIErrorCause{ - APIError: rest_model.APIError{ - Data: genericErr.DataMap, - Message: genericErr.Error(), - }, - } - } else { - ret.Cause = &rest_model.APIErrorCause{ - APIError: rest_model.APIError{ - Code: errorz.UnhandledCode, - Message: e.Cause.Error(), - }, - } - } - - } - - return ret -} - -type GenericCauseError struct { - Message string - DataMap map[string]interface{} -} - -func (e GenericCauseError) Error() string { - return e.Message -} - -func (e *GenericCauseError) MarshalJSON() ([]byte, error) { - data, err := gabs.Consume(e.DataMap) //gabs to avoid zero values being omitted - - if err != nil { - return nil, err - } - - return data.Bytes(), nil -} diff --git a/controller/apierror/bodyparser.go b/controller/apierror/bodyparser.go deleted file mode 100644 index a0d6f6b61..000000000 --- a/controller/apierror/bodyparser.go +++ /dev/null @@ -1,94 +0,0 @@ -/* - Copyright NetFoundry, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package apierror - -import ( - "encoding/json" - "fmt" - "strings" -) - -type BodyParseTypeError struct { - Near string `json:"near"` - ExpectedType string `json:"expectedType"` - EncounteredType string `json:"encounteredType"` - Offset int64 `json:"totalOffset"` - Line int64 `json:"line"` -} - -func (e BodyParseTypeError) Error() string { - return fmt.Sprintf("Expected type %s, but encountered %s near %s", e.ExpectedType, e.ExpectedType, e.Near) -} - -func NewBodyParseTypeError(e *json.UnmarshalTypeError, body string) *BodyParseTypeError { - nearStart := e.Offset - 10 - nearEnd := e.Offset - - if nearStart < 0 { - nearStart = 0 - } - - near := body[nearStart:nearEnd] - - bodyToError := body[:e.Offset] - - line := strings.Count(bodyToError, "\n") + 1 - - return &BodyParseTypeError{ - ExpectedType: e.Type.String(), - EncounteredType: e.Value, - Near: near, - Offset: e.Offset, - Line: int64(line), - } -} - -type BodyParseSyntaxError struct { - Near string `json:"near"` - Offset int64 `json:"totalOffset"` - Line int64 `json:"line"` -} - -func (e BodyParseSyntaxError) Error() string { - return fmt.Sprintf("Invalid syntax on line %d", e.Line) -} - -func NewBodyParseSyntaxError(e *json.SyntaxError, body string) *BodyParseSyntaxError { - nearStart := e.Offset - 10 - nearEnd := e.Offset - - if nearStart < 0 { - nearStart = 0 - nearEnd = 5 - } - - if nearEnd > int64(len(body)) { - nearEnd = int64(len(body)) - } - - near := body[nearStart:nearEnd] - - bodyToError := body[:e.Offset] - - line := strings.Count(bodyToError, "\n") + 1 - - return &BodyParseSyntaxError{ - Near: near, - Offset: e.Offset, - Line: int64(line), - } -} diff --git a/controller/apierror/helpers.go b/controller/apierror/helpers.go index c7464516c..97bf5405c 100644 --- a/controller/apierror/helpers.go +++ b/controller/apierror/helpers.go @@ -17,6 +17,7 @@ package apierror import ( + "github.com/openziti/fabric/controller/apierror" "github.com/openziti/foundation/util/errorz" ) @@ -213,7 +214,7 @@ func NewInvalidUuid(val string) *errorz.ApiError { Code: InvalidUuidCode, Message: InvalidUuidMessage, Status: InvalidUuidStatus, - Cause: &GenericCauseError{ + Cause: &apierror.GenericCauseError{ Message: "invalid uuid", DataMap: map[string]interface{}{ "uuid": val, diff --git a/controller/apierror/json.go b/controller/apierror/json.go deleted file mode 100644 index 3c20cd374..000000000 --- a/controller/apierror/json.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright NetFoundry, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package apierror - -import ( - "encoding/json" -) - -func GetJsonParseError(e error, body []byte) error { - if ute, ok := e.(*json.UnmarshalTypeError); ok { - e = NewBodyParseTypeError(ute, string(body)) - } else if se, ok := e.(*json.SyntaxError); ok { - e = NewBodyParseSyntaxError(se, string(body)) - } - return e -} diff --git a/controller/env/appenv.go b/controller/env/appenv.go index 5a9628373..ff8cdb0cf 100644 --- a/controller/env/appenv.go +++ b/controller/env/appenv.go @@ -18,7 +18,6 @@ package env import ( "bytes" - "context" "crypto/ecdsa" "crypto/rsa" "crypto/tls" @@ -43,6 +42,7 @@ import ( clientOperations "github.com/openziti/edge/rest_client_api_server/operations" managementServer "github.com/openziti/edge/rest_management_api_server" managementOperations "github.com/openziti/edge/rest_management_api_server/operations" + "github.com/openziti/fabric/controller/api" "github.com/openziti/fabric/controller/network" "github.com/openziti/fabric/controller/xctrl" "github.com/openziti/fabric/controller/xmgmt" @@ -253,7 +253,6 @@ func (ae *AppEnv) FillRequestContext(rc *response.RequestContext) error { } } - if rc.ApiSession != nil { //updates for api session timeouts ae.GetHandlers().ApiSession.MarkActivityById(rc.ApiSession.Id) @@ -428,13 +427,12 @@ func (ae *AppEnv) CreateRequestContext(rw http.ResponseWriter, r *http.Request) requestContext := &response.RequestContext{ Id: rid, + ResponseWriter: rw, + Request: r, Body: body, Identity: nil, ApiSession: nil, ActivePermissions: []string{}, - ResponseWriter: rw, - Request: r, - EventLogger: &DefaultEventLogger{Ae: ae}, } requestContext.Responder = response.NewResponder(requestContext) @@ -442,26 +440,16 @@ func (ae *AppEnv) CreateRequestContext(rw http.ResponseWriter, r *http.Request) return requestContext } -// ContextKey is used a custom type to avoid accidental context key collisions -type ContextKey string - -const EdgeContextKey = ContextKey("edgeContext") - -func AddRequestContextToHttpContext(r *http.Request, rc *response.RequestContext) { - ctx := context.WithValue(r.Context(), EdgeContextKey, rc) - *r = *r.WithContext(ctx) -} - func GetRequestContextFromHttpContext(r *http.Request) (*response.RequestContext, error) { - val := r.Context().Value(EdgeContextKey) + val := r.Context().Value(api.ZitiContextKey) if val == nil { - return nil, fmt.Errorf("value for key %s no found in context", EdgeContextKey) + return nil, fmt.Errorf("value for key %s no found in context", api.ZitiContextKey) } requestContext := val.(*response.RequestContext) if requestContext == nil { - return nil, fmt.Errorf("value for key %s is not a request context", EdgeContextKey) + return nil, fmt.Errorf("value for key %s is not a request context", api.ZitiContextKey) } return requestContext, nil @@ -472,16 +460,20 @@ func (ae *AppEnv) IsAllowed(responderFunc func(ae *AppEnv, rc *response.RequestC rc, err := GetRequestContextFromHttpContext(request) - if err != nil { - pfxlog.Logger().WithError(err).Error("could not retrieve request context") - response.RespondWithError(writer, rc.Request, eid.New(), producer, err) - return + if rc == nil { + rc = ae.CreateRequestContext(writer, request) } rc.SetProducer(producer) rc.SetEntityId(entityId) rc.SetEntitySubId(entitySubId) + if err != nil { + pfxlog.Logger().WithError(err).Error("could not retrieve request context") + rc.RespondWithError(err) + return + } + for _, permission := range permissions { if !permission.IsAllowed(rc.ActivePermissions...) { rc.RespondWithApiError(errorz.NewUnauthorized()) diff --git a/controller/env/context.go b/controller/env/context.go new file mode 100644 index 000000000..4bf387952 --- /dev/null +++ b/controller/env/context.go @@ -0,0 +1,30 @@ +package env + +import ( + "bytes" + "github.com/openziti/edge/controller/response" + "github.com/openziti/edge/eid" + "io/ioutil" + "net/http" +) + +func NewRequestContext(rw http.ResponseWriter, r *http.Request) *response.RequestContext { + rid := eid.New() + + body, _ := ioutil.ReadAll(r.Body) + r.Body = ioutil.NopCloser(bytes.NewReader(body)) + + requestContext := &response.RequestContext{ + Id: rid, + ResponseWriter: rw, + Request: r, + Body: body, + Identity: nil, + ApiSession: nil, + ActivePermissions: []string{}, + } + + requestContext.Responder = response.NewResponder(requestContext) + + return requestContext +} diff --git a/controller/env/helpers.go b/controller/env/helpers.go index 4385d349a..cfec33a67 100644 --- a/controller/env/helpers.go +++ b/controller/env/helpers.go @@ -1,13 +1,9 @@ package env import ( - "errors" - "fmt" openApiErrors "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" + "github.com/michaelquigley/pfxlog" "github.com/openziti/edge/controller/apierror" - "github.com/openziti/edge/controller/response" - "github.com/openziti/edge/eid" "github.com/openziti/foundation/util/errorz" "net/http" ) @@ -54,26 +50,14 @@ func ServeError(rw http.ResponseWriter, r *http.Request, inErr error) { } apiError.Cause = openApiError - response.RespondWithApiError(rw, r, eid.New(), runtime.JSONProducer(), apiError) + NewRequestContext(rw, r).RespondWithApiError(apiError) return } requestContext, err := GetRequestContextFromHttpContext(r) - - if err != nil { - apiError := errorz.NewUnhandled(err) - - apiError.Cause = fmt.Errorf("error retrieveing request context: %w", err) - - response.RespondWithApiError(rw, r, eid.New(), runtime.JSONProducer(), apiError) - return - } - - if requestContext == nil { - apiError := errorz.NewUnhandled(err) - apiError.Cause = errors.New("expected request context is nil") - response.RespondWithApiError(rw, r, eid.New(), runtime.JSONProducer(), apiError) - return + if requestContext == nil || err != nil { + pfxlog.Logger().WithError(err).Error("failed to retrieve request context") + requestContext = NewRequestContext(rw, r) } requestContext.RespondWithError(inErr) diff --git a/controller/internal/routes/authenticator_router.go b/controller/internal/routes/authenticator_router.go index 27020e476..30861adb2 100644 --- a/controller/internal/routes/authenticator_router.go +++ b/controller/internal/routes/authenticator_router.go @@ -22,6 +22,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/authenticator" + "github.com/openziti/fabric/controller/api" ) func init() { @@ -96,7 +97,7 @@ func (r *AuthenticatorRouter) Update(ae *env.AppEnv, rc *response.RequestContext } func (r *AuthenticatorRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params authenticator.PatchAuthenticatorParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { model := MapPatchAuthenticatorToModel(params.ID, params.Authenticator) if fields.IsUpdated("password") { diff --git a/controller/internal/routes/base_router.go b/controller/internal/routes/base_router.go index 60db24011..a88826467 100644 --- a/controller/internal/routes/base_router.go +++ b/controller/internal/routes/base_router.go @@ -17,14 +17,13 @@ package routes import ( - "encoding/json" "fmt" "github.com/michaelquigley/pfxlog" - "github.com/openziti/edge/controller/apierror" "github.com/openziti/edge/controller/env" "github.com/openziti/edge/controller/response" - "github.com/openziti/edge/controller/schema" "github.com/openziti/edge/rest_model" + "github.com/openziti/fabric/controller/api" + "github.com/openziti/fabric/controller/apierror" "github.com/openziti/fabric/controller/models" "github.com/openziti/foundation/storage/ast" "github.com/openziti/foundation/storage/boltz" @@ -38,70 +37,6 @@ const ( EntityNameSelf = "self" ) -type JsonFields map[string]bool - -func (j JsonFields) IsUpdated(key string) bool { - _, ok := j[key] - return ok -} - -func (j JsonFields) AddField(key string) { - j[key] = true -} - -func (j JsonFields) ConcatNestedNames() JsonFields { - for key, val := range j { - if strings.Contains(key, ".") { - delete(j, key) - key = strings.ReplaceAll(key, ".", "") - j[key] = val - } - } - return j -} - -func (j JsonFields) FilterMaps(mapNames ...string) JsonFields { - nameMap := map[string]string{} - for _, name := range mapNames { - nameMap[name] = name + "." - } - for key := range j { - for name, dotName := range nameMap { - if strings.HasPrefix(key, dotName) { - delete(j, key) - j[name] = true - break - } - } - } - return j -} - -func getFields(body []byte) (JsonFields, error) { - jsonMap := map[string]interface{}{} - err := json.Unmarshal(body, &jsonMap) - - if err != nil { - return nil, apierror.GetJsonParseError(err, body) - } - - resultMap := JsonFields{} - getJsonFields("", jsonMap, resultMap) - return resultMap, nil -} - -func getJsonFields(prefix string, m map[string]interface{}, result JsonFields) { - for k, v := range m { - name := k - if subMap, ok := v.(map[string]interface{}); ok { - getJsonFields(prefix+name+".", subMap, result) - } else { - isSet := v != nil - result[prefix+name] = isSet - } - } -} - func modelToApi(ae *env.AppEnv, rc *response.RequestContext, mapper ModelToApiMapper, es []models.Entity) ([]interface{}, error) { apiEntities := make([]interface{}, 0) @@ -237,7 +172,7 @@ func CreateWithResponder(rc *response.RequestContext, rsp response.Responder, li return } - if sve, ok := err.(*schema.ValidationErrors); ok { + if sve, ok := err.(*apierror.ValidationErrors); ok { rc.RespondWithValidationErrors(sve) return } @@ -349,7 +284,7 @@ func UpdateAllowEmptyBody(rc *response.RequestContext, updateF ModelUpdateF) { return } - if sve, ok := err.(*schema.ValidationErrors); ok { + if sve, ok := err.(*apierror.ValidationErrors); ok { rc.RespondWithValidationErrors(sve) return } @@ -361,7 +296,7 @@ func UpdateAllowEmptyBody(rc *response.RequestContext, updateF ModelUpdateF) { rc.RespondWithEmptyOk() } -type ModelPatchF func(id string, fields JsonFields) error +type ModelPatchF func(id string, fields api.JsonFields) error func Patch(rc *response.RequestContext, patchF ModelPatchF) { id, err := rc.GetEntityId() @@ -373,7 +308,7 @@ func Patch(rc *response.RequestContext, patchF ModelPatchF) { return } - jsonFields, err := getFields(rc.Body) + jsonFields, err := api.GetFields(rc.Body) if err != nil { rc.RespondWithCouldNotParseBody(err) } @@ -390,7 +325,7 @@ func Patch(rc *response.RequestContext, patchF ModelPatchF) { return } - if sve, ok := err.(*schema.ValidationErrors); ok { + if sve, ok := err.(*apierror.ValidationErrors); ok { rc.RespondWithValidationErrors(sve) return } @@ -514,7 +449,7 @@ func ListAssociations(rc *response.RequestContext, listF listAssocF) { rc.RespondWithError(err) return } - + queryOptions, err := GetModelQueryOptionsFromRequest(rc.Request) if err != nil { diff --git a/controller/internal/routes/base_router_test.go b/controller/internal/routes/base_router_test.go index 717e936cd..eb5e04fb2 100644 --- a/controller/internal/routes/base_router_test.go +++ b/controller/internal/routes/base_router_test.go @@ -18,6 +18,7 @@ package routes import ( "encoding/json" + "github.com/openziti/fabric/controller/api" "reflect" "testing" @@ -52,13 +53,13 @@ func Test_getFields(t *testing.T) { tests := []struct { name string body []byte - want JsonFields + want api.JsonFields wantErr bool }{ { name: "test", body: []byte(test), - want: JsonFields{ + want: api.JsonFields{ "name": true, "terminatorStrategy": true, "configs": true, @@ -68,7 +69,7 @@ func Test_getFields(t *testing.T) { { name: "test2", body: test2Bytes, - want: JsonFields{ + want: api.JsonFields{ "name": true, "terminatorStrategy": true, "roleAttributes": true, @@ -80,7 +81,7 @@ func Test_getFields(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := getFields(tt.body) + got, err := api.GetFields(tt.body) got.FilterMaps("tags") if (err != nil) != tt.wantErr { t.Errorf("getFields() error = %v, wantErr %v", err, tt.wantErr) @@ -96,17 +97,17 @@ func Test_getFields(t *testing.T) { func TestJsonFields_ConcatNestedNames(t *testing.T) { tests := []struct { name string - j JsonFields - want JsonFields + j api.JsonFields + want api.JsonFields }{ {"test", - JsonFields{ + api.JsonFields{ "Name": true, "This.Is.A.Longer.Name": true, "EgressRouter": true, "Address": false, }, - JsonFields{ + api.JsonFields{ "Name": true, "ThisIsALongerName": true, "EgressRouter": true, diff --git a/controller/internal/routes/ca_router.go b/controller/internal/routes/ca_router.go index 2ae190e1c..eb6387c5c 100644 --- a/controller/internal/routes/ca_router.go +++ b/controller/internal/routes/ca_router.go @@ -28,6 +28,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/certificate_authority" + "github.com/openziti/fabric/controller/api" "github.com/openziti/foundation/storage/boltz" "github.com/openziti/sdk-golang/ziti/config" "github.com/pkg/errors" @@ -113,7 +114,7 @@ func (r *CaRouter) Update(ae *env.AppEnv, rc *response.RequestContext, params ce } func (r *CaRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params certificate_authority.PatchCaParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.Ca.Patch(MapPatchCaToModel(params.ID, params.Ca), fields.FilterMaps("tags")) }) } diff --git a/controller/internal/routes/config_router.go b/controller/internal/routes/config_router.go index 109654a72..bfa562280 100644 --- a/controller/internal/routes/config_router.go +++ b/controller/internal/routes/config_router.go @@ -23,6 +23,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/config" + "github.com/openziti/fabric/controller/api" ) func init() { @@ -101,7 +102,7 @@ func (r *ConfigRouter) Update(ae *env.AppEnv, rc *response.RequestContext, param } func (r *ConfigRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params config.PatchConfigParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.Config.Patch(MapPatchConfigToModel(params.ID, params.Config), fields.FilterMaps("tags", "data")) }) } diff --git a/controller/internal/routes/config_type_router.go b/controller/internal/routes/config_type_router.go index a2ea26bf5..2bd2e5244 100644 --- a/controller/internal/routes/config_type_router.go +++ b/controller/internal/routes/config_type_router.go @@ -23,6 +23,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/config" + "github.com/openziti/fabric/controller/api" ) func init() { @@ -119,7 +120,7 @@ func (r *ConfigTypeRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, pa return } - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.ConfigType.Patch(MapPatchConfigTypeToModel(params.ID, params.ConfigType), fields.FilterMaps("tags", "schema")) }) } diff --git a/controller/internal/routes/current_identity_authenticator_router.go b/controller/internal/routes/current_identity_authenticator_router.go index b23988547..412bc9201 100644 --- a/controller/internal/routes/current_identity_authenticator_router.go +++ b/controller/internal/routes/current_identity_authenticator_router.go @@ -26,6 +26,7 @@ import ( clientCurrentApiSession "github.com/openziti/edge/rest_client_api_server/operations/current_api_session" managementCurrentApiSession "github.com/openziti/edge/rest_management_api_server/operations/current_api_session" "github.com/openziti/edge/rest_model" + "github.com/openziti/fabric/controller/api" "github.com/openziti/foundation/storage/boltz" "github.com/openziti/foundation/util/errorz" ) @@ -146,7 +147,7 @@ func (r *CurrentIdentityAuthenticatorRouter) Update(ae *env.AppEnv, rc *response } func (r *CurrentIdentityAuthenticatorRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, authenticator *rest_model.AuthenticatorPatchWithCurrent) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.Authenticator.PatchSelf(MapPatchAuthenticatorWithCurrentToModel(id, rc.Identity.Id, authenticator), fields.FilterMaps("tags")) }) } diff --git a/controller/internal/routes/edge_router_policy_router.go b/controller/internal/routes/edge_router_policy_router.go index 62c628ddc..793e43bb7 100644 --- a/controller/internal/routes/edge_router_policy_router.go +++ b/controller/internal/routes/edge_router_policy_router.go @@ -22,6 +22,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/edge_router_policy" + "github.com/openziti/fabric/controller/api" ) func init() { @@ -100,7 +101,7 @@ func (r *EdgeRouterPolicyRouter) Update(ae *env.AppEnv, rc *response.RequestCont } func (r *EdgeRouterPolicyRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params edge_router_policy.PatchEdgeRouterPolicyParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.EdgeRouterPolicy.Patch(MapPatchEdgeRouterPolicyToModel(params.ID, params.Policy), fields.FilterMaps("tags")) }) } diff --git a/controller/internal/routes/edge_router_router.go b/controller/internal/routes/edge_router_router.go index 7eacc91b6..0c85ee3e1 100644 --- a/controller/internal/routes/edge_router_router.go +++ b/controller/internal/routes/edge_router_router.go @@ -22,6 +22,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/edge_router" + "github.com/openziti/fabric/controller/api" "github.com/openziti/fabric/controller/models" "github.com/openziti/foundation/storage/ast" ) @@ -123,7 +124,7 @@ func (r *EdgeRouterRouter) Update(ae *env.AppEnv, rc *response.RequestContext, p } func (r *EdgeRouterRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params edge_router.PatchEdgeRouterParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.EdgeRouter.Patch(MapPatchEdgeRouterToModel(params.ID, params.EdgeRouter), fields) }) } diff --git a/controller/internal/routes/enroll_router.go b/controller/internal/routes/enroll_router.go index bc742dceb..a0fb9ae0a 100644 --- a/controller/internal/routes/enroll_router.go +++ b/controller/internal/routes/enroll_router.go @@ -169,7 +169,7 @@ func (ro *EnrollRouter) enrollHandler(ae *env.AppEnv, rc *response.RequestContex } if producer, ok := rc.GetProducer().(*env.PemProducer); ok { - response.Respond(rc.ResponseWriter, rc.Request.URL.Path, rc.Id, producer, result.TextContent, http.StatusOK) + rc.RespondWithProducer(producer, result.TextContent, http.StatusOK) return } diff --git a/controller/internal/routes/identity_router.go b/controller/internal/routes/identity_router.go index f8f64f099..8c74cd782 100644 --- a/controller/internal/routes/identity_router.go +++ b/controller/internal/routes/identity_router.go @@ -29,6 +29,7 @@ import ( "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/identity" "github.com/openziti/edge/rest_model" + "github.com/openziti/fabric/controller/api" "github.com/openziti/fabric/controller/models" "github.com/openziti/fabric/logcontext" "github.com/openziti/foundation/storage/ast" @@ -201,7 +202,7 @@ func (r *IdentityRouter) Update(ae *env.AppEnv, rc *response.RequestContext, par } func (r *IdentityRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params identity.PatchIdentityParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { fields = fields.FilterMaps(boltz.FieldTags, persistence.FieldIdentityAppData, persistence.FieldIdentityServiceHostingCosts, persistence.FieldIdentityServiceHostingPrecedences) return ae.Handlers.Identity.Patch(MapPatchIdentityToModel(params.ID, params.Identity, getIdentityTypeId(ae, params.Identity.Type)), fields) }) diff --git a/controller/internal/routes/posture_check_router.go b/controller/internal/routes/posture_check_router.go index 2a42b39db..674634fb0 100644 --- a/controller/internal/routes/posture_check_router.go +++ b/controller/internal/routes/posture_check_router.go @@ -23,6 +23,7 @@ import ( "github.com/openziti/edge/controller/persistence" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/posture_checks" + "github.com/openziti/fabric/controller/api" "github.com/openziti/fabric/controller/models" ) @@ -132,7 +133,7 @@ func (r *PostureCheckRouter) Update(ae *env.AppEnv, rc *response.RequestContext, } func (r *PostureCheckRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params posture_checks.PatchPostureCheckParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { check := MapPatchPostureCheckToModel(params.ID, params.PostureCheck) if fields.IsUpdated("operatingSystems") { diff --git a/controller/internal/routes/router_router.go b/controller/internal/routes/router_router.go index af325c732..740d9d744 100644 --- a/controller/internal/routes/router_router.go +++ b/controller/internal/routes/router_router.go @@ -23,6 +23,7 @@ import ( "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/router" "github.com/openziti/edge/rest_model" + "github.com/openziti/fabric/controller/api" ) func init() { @@ -117,7 +118,7 @@ func (r *TransitRouterRouter) Update(ae *env.AppEnv, rc *response.RequestContext } func (r *TransitRouterRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, routerId string, router *rest_model.RouterPatch) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.TransitRouter.Patch(MapPatchTransitRouterToModel(routerId, router), fields.ConcatNestedNames().FilterMaps("tags"), false) }) } diff --git a/controller/internal/routes/service_edge_router_policy_router.go b/controller/internal/routes/service_edge_router_policy_router.go index 564eb179a..28b2f2330 100644 --- a/controller/internal/routes/service_edge_router_policy_router.go +++ b/controller/internal/routes/service_edge_router_policy_router.go @@ -22,6 +22,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/service_edge_router_policy" + "github.com/openziti/fabric/controller/api" ) func init() { @@ -100,7 +101,7 @@ func (r *ServiceEdgeRouterPolicyRouter) Update(ae *env.AppEnv, rc *response.Requ } func (r *ServiceEdgeRouterPolicyRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params service_edge_router_policy.PatchServiceEdgeRouterPolicyParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.ServiceEdgeRouterPolicy.Patch(MapPatchServiceEdgeRouterPolicyToModel(params.ID, params.Policy), fields.FilterMaps("tags")) }) } diff --git a/controller/internal/routes/service_policy_router.go b/controller/internal/routes/service_policy_router.go index 214d7066e..4de69ac9a 100644 --- a/controller/internal/routes/service_policy_router.go +++ b/controller/internal/routes/service_policy_router.go @@ -22,6 +22,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/service_policy" + "github.com/openziti/fabric/controller/api" ) func init() { @@ -104,7 +105,7 @@ func (r *ServicePolicyRouter) Update(ae *env.AppEnv, rc *response.RequestContext } func (r *ServicePolicyRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params service_policy.PatchServicePolicyParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.ServicePolicy.Patch(MapPatchServicePolicyToModel(params.ID, params.Policy), fields.FilterMaps("tags")) }) } @@ -114,7 +115,7 @@ func (r *ServicePolicyRouter) ListServices(ae *env.AppEnv, rc *response.RequestC } func (r *ServicePolicyRouter) ListIdentities(ae *env.AppEnv, rc *response.RequestContext) { - ListAssociationWithHandler(ae, rc, ae.Handlers.ServicePolicy, ae.Handlers.Identity, MapIdentityToRestEntity) + ListAssociationWithHandler(ae, rc, ae.Handlers.ServicePolicy, ae.Handlers.Identity, MapIdentityToRestEntity) } func (r *ServicePolicyRouter) ListPostureChecks(ae *env.AppEnv, rc *response.RequestContext) { diff --git a/controller/internal/routes/service_router.go b/controller/internal/routes/service_router.go index b7db656ad..6abf2fe75 100644 --- a/controller/internal/routes/service_router.go +++ b/controller/internal/routes/service_router.go @@ -22,6 +22,7 @@ import ( "github.com/michaelquigley/pfxlog" clientService "github.com/openziti/edge/rest_client_api_server/operations/service" managementService "github.com/openziti/edge/rest_management_api_server/operations/service" + "github.com/openziti/fabric/controller/api" "github.com/openziti/fabric/controller/models" "github.com/openziti/foundation/metrics" "github.com/openziti/foundation/storage/boltz" @@ -243,7 +244,7 @@ func (r *ServiceRouter) Update(ae *env.AppEnv, rc *response.RequestContext, para } func (r *ServiceRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params managementService.PatchServiceParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.EdgeService.Patch(MapPatchServiceToModel(params.ID, params.Service), fields.ConcatNestedNames().FilterMaps("tags")) }) } diff --git a/controller/internal/routes/spec_router.go b/controller/internal/routes/spec_router.go index 633b3f2cf..c88914832 100644 --- a/controller/internal/routes/spec_router.go +++ b/controller/internal/routes/spec_router.go @@ -21,7 +21,6 @@ import ( "fmt" "github.com/go-openapi/runtime/middleware" "github.com/michaelquigley/pfxlog" - "github.com/openziti/edge/build" "github.com/openziti/edge/controller/env" "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" @@ -30,6 +29,7 @@ import ( "github.com/openziti/edge/rest_management_api_server" managementInformational "github.com/openziti/edge/rest_management_api_server/operations/informational" "github.com/openziti/edge/rest_model" + "github.com/openziti/fabric/build" "github.com/openziti/fabric/controller/models" "time" ) diff --git a/controller/internal/routes/terminator_router.go b/controller/internal/routes/terminator_router.go index 56edea549..7f3318159 100644 --- a/controller/internal/routes/terminator_router.go +++ b/controller/internal/routes/terminator_router.go @@ -22,6 +22,7 @@ import ( "github.com/openziti/edge/controller/internal/permissions" "github.com/openziti/edge/controller/response" "github.com/openziti/edge/rest_management_api_server/operations/terminator" + "github.com/openziti/fabric/controller/api" ) func init() { @@ -90,7 +91,7 @@ func (r *TerminatorRouter) Update(ae *env.AppEnv, rc *response.RequestContext, p } func (r *TerminatorRouter) Patch(ae *env.AppEnv, rc *response.RequestContext, params terminator.PatchTerminatorParams) { - Patch(rc, func(id string, fields JsonFields) error { + Patch(rc, func(id string, fields api.JsonFields) error { return ae.Handlers.Terminator.Patch(MapPatchTerminatorToModel(params.ID, params.Terminator), fields.FilterMaps("tags")) }) } diff --git a/controller/internal/routes/version_router.go b/controller/internal/routes/version_router.go index 89538b2af..05da15449 100644 --- a/controller/internal/routes/version_router.go +++ b/controller/internal/routes/version_router.go @@ -19,7 +19,6 @@ package routes import ( "github.com/go-openapi/runtime/middleware" - "github.com/openziti/edge/build" "github.com/openziti/edge/controller" "github.com/openziti/edge/controller/env" "github.com/openziti/edge/controller/internal/permissions" @@ -27,6 +26,7 @@ import ( clientInformational "github.com/openziti/edge/rest_client_api_server/operations/informational" managementInformational "github.com/openziti/edge/rest_management_api_server/operations/informational" "github.com/openziti/edge/rest_model" + "github.com/openziti/fabric/build" "github.com/openziti/fabric/xweb" "runtime" "sync" diff --git a/controller/model/config_model.go b/controller/model/config_model.go index 1ef9b41f0..c1a97f8c3 100644 --- a/controller/model/config_model.go +++ b/controller/model/config_model.go @@ -18,7 +18,7 @@ package model import ( "github.com/openziti/edge/controller/persistence" - "github.com/openziti/edge/controller/schema" + "github.com/openziti/fabric/controller/apierror" "github.com/openziti/fabric/controller/models" "github.com/openziti/foundation/storage/boltz" "github.com/openziti/foundation/util/errorz" @@ -63,7 +63,7 @@ func (entity *Config) toBoltEntity(tx *bbolt.Tx, handler Handler) (boltz.Entity, return nil, err } if !result.Valid() { - return nil, schema.NewValidationErrors(result) + return nil, apierror.NewValidationErrors(result) } } diff --git a/controller/model/enrollment.go b/controller/model/enrollment.go index aa6858e82..086ed9279 100644 --- a/controller/model/enrollment.go +++ b/controller/model/enrollment.go @@ -21,6 +21,7 @@ import ( "encoding/json" "github.com/go-openapi/runtime" "github.com/openziti/edge/controller/apierror" + fabricApiError "github.com/openziti/fabric/controller/apierror" "io/ioutil" "net/http" "strings" @@ -152,7 +153,7 @@ func (context *EnrollmentContextHttp) FillFromHttpRequest(request *http.Request) err := json.Unmarshal(body, &data) if err != nil { - err = apierror.GetJsonParseError(err, body) + err = fabricApiError.GetJsonParseError(err, body) apiErr := apierror.NewCouldNotParseBody(err) apiErr.AppendCause = true return apiErr diff --git a/controller/model/enrollment_mod_ottca.go b/controller/model/enrollment_mod_ottca.go index 0f258351e..524919bbf 100644 --- a/controller/model/enrollment_mod_ottca.go +++ b/controller/model/enrollment_mod_ottca.go @@ -22,6 +22,7 @@ import ( "github.com/openziti/edge/controller/apierror" "github.com/openziti/edge/controller/persistence" "github.com/openziti/edge/internal/cert" + fabricApiError "github.com/openziti/fabric/controller/apierror" "github.com/openziti/fabric/controller/models" ) @@ -117,7 +118,7 @@ func (module *EnrollModuleOttCa) Process(ctx EnrollmentContext) (*EnrollmentResu if existing != nil { apiError := apierror.NewCertInUse() - apiError.Cause = &apierror.GenericCauseError{ + apiError.Cause = &fabricApiError.GenericCauseError{ DataMap: map[string]interface{}{ "fingerprint": fingerprint, }, diff --git a/controller/model/session_handlers.go b/controller/model/session_handlers.go index 4b86a68c8..de3d2267c 100644 --- a/controller/model/session_handlers.go +++ b/controller/model/session_handlers.go @@ -21,6 +21,7 @@ import ( "github.com/lucsky/cuid" "github.com/openziti/edge/controller/apierror" "github.com/openziti/edge/controller/persistence" + fabricApiError "github.com/openziti/fabric/controller/apierror" "github.com/openziti/fabric/controller/models" "github.com/openziti/foundation/storage/ast" "github.com/openziti/foundation/storage/boltz" @@ -50,7 +51,7 @@ type SessionPostureResult struct { Passed bool Failure *PostureSessionRequestFailure PassingPolicyIds []string - Cause *apierror.GenericCauseError + Cause *fabricApiError.GenericCauseError } func (handler *SessionHandler) EvaluatePostureForService(identityId, apiSessionId, sessionType, serviceId, serviceName string) *SessionPostureResult { @@ -128,7 +129,7 @@ func (handler *SessionHandler) EvaluatePostureForService(identityId, apiSessionI sessionFailure.PolicyFailures = append(sessionFailure.PolicyFailures, policyFailure) } - cause := &apierror.GenericCauseError{ + cause := &fabricApiError.GenericCauseError{ Message: fmt.Sprintf("Failed to pass posture checks for service policies: %v", failedPolicyIds), DataMap: failureMap, } diff --git a/controller/response/context.go b/controller/response/context.go index b4fe30943..38b5afc93 100644 --- a/controller/response/context.go +++ b/controller/response/context.go @@ -35,13 +35,28 @@ type RequestContext struct { ActivePermissions []string ResponseWriter http.ResponseWriter Request *http.Request - EventLogger EventLogger SessionToken string entityId string entitySubId string Body []byte } +func (rc *RequestContext) GetId() string { + return rc.Id +} + +func (rc *RequestContext) GetBody() []byte { + return rc.Body +} + +func (rc *RequestContext) GetRequest() *http.Request { + return rc.Request +} + +func (rc *RequestContext) GetResponseWriter() http.ResponseWriter { + return rc.ResponseWriter +} + type EventLogger interface { Log(actorType, actorId, eventType, entityType, entityId, formatString string, formatData []string, data map[interface{}]interface{}) } diff --git a/controller/response/headers.go b/controller/response/headers.go index 57e877684..c854e84ca 100644 --- a/controller/response/headers.go +++ b/controller/response/headers.go @@ -17,7 +17,7 @@ package response import ( - "github.com/openziti/edge/build" + "github.com/openziti/fabric/build" "strconv" ) @@ -41,4 +41,4 @@ func AddSessionHeaders(rc *RequestContext) { rc.ResponseWriter.Header().Set(ApiSessionExpirationSecondsHeader, strconv.FormatInt(int64(rc.ApiSession.ExpirationDuration.Seconds()), 10)) rc.ResponseWriter.Header().Set(ApiSessionExpiresAtHeader, rc.ApiSession.ExpiresAt.String()) } -} \ No newline at end of file +} diff --git a/controller/response/responder.go b/controller/response/responder.go index e82d63ea2..ebad3ec78 100644 --- a/controller/response/responder.go +++ b/controller/response/responder.go @@ -17,76 +17,129 @@ package response import ( - "github.com/go-openapi/runtime" - "github.com/michaelquigley/pfxlog" - "github.com/openziti/edge/controller/apierror" - "github.com/openziti/edge/controller/schema" + "fmt" + "github.com/go-openapi/errors" "github.com/openziti/edge/internal/version" "github.com/openziti/edge/rest_model" + "github.com/openziti/fabric/controller/api" + "github.com/openziti/fabric/controller/apierror" "github.com/openziti/foundation/util/errorz" "net/http" - "strings" ) //todo: rename to Responder, remove old Responder and RequestResponder type Responder interface { - Respond(data interface{}, httpStatus int) + api.Responder RespondWithOk(data interface{}, meta *rest_model.Meta) - RespondWithEmptyOk() - RespondWithError(err error) - RespondWithApiError(apiError *errorz.ApiError) - SetProducer(producer runtime.Producer) - GetProducer() runtime.Producer - RespondWithCouldNotReadBody(err error) - RespondWithCouldNotParseBody(err error) - RespondWithValidationErrors(errors *schema.ValidationErrors) - RespondWithNotFound() - RespondWithNotFoundWithCause(cause error) - RespondWithFieldError(fe *errorz.FieldError) RespondWithCreatedId(id string, link rest_model.Link) } +type EdgeResponseMapper struct{} + +func (EdgeResponseMapper) EmptyOkData() interface{} { + return &rest_model.Empty{ + Data: map[string]interface{}{}, + Meta: &rest_model.Meta{}, + } +} + +func (self EdgeResponseMapper) MapApiError(requestId string, apiError *errorz.ApiError) interface{} { + return &rest_model.APIErrorEnvelope{ + Error: self.toRestModel(apiError, requestId), + Meta: &rest_model.Meta{ + APIEnrollmentVersion: version.GetApiEnrollmentVersion(), + APIVersion: version.GetApiVersion(), + }, + } +} + +func (self EdgeResponseMapper) toRestModel(e *errorz.ApiError, requestId string) *rest_model.APIError { + ret := &rest_model.APIError{ + Args: nil, + Code: e.Code, + Message: e.Message, + RequestID: requestId, + } + + if e.Cause != nil { + + //unwrap first error in composite error + compositeErr, ok := e.Cause.(*errors.CompositeError) + for ok { + e.Cause = compositeErr.Errors[0] + compositeErr, ok = e.Cause.(*errors.CompositeError) + } + + if causeApiError, ok := e.Cause.(*errorz.ApiError); ok { + //standard apierror + ret.Cause = &rest_model.APIErrorCause{ + APIError: *self.toRestModel(causeApiError, requestId), + } + } else if causeJsonSchemaError, ok := e.Cause.(*apierror.ValidationErrors); ok { + //only possible from config type JSON schema validation + ret.Cause = &rest_model.APIErrorCause{ + APIFieldError: rest_model.APIFieldError{ + Field: causeJsonSchemaError.Errors[0].Field, + Reason: causeJsonSchemaError.Errors[0].Error(), + Value: fmt.Sprintf("%v", causeJsonSchemaError.Errors[0].Value), + }, + } + } else if causeFieldErr, ok := e.Cause.(*errorz.FieldError); ok { + //authenticator modules and enrollment only + //todo: see if we can remove this by not using FieldError + ret.Cause = &rest_model.APIErrorCause{ + APIFieldError: rest_model.APIFieldError{ + Field: causeFieldErr.FieldName, + Value: fmt.Sprintf("%v", causeFieldErr.FieldValue), + Reason: causeFieldErr.Reason, + }, + } + if ret.Code == errorz.InvalidFieldCode { + ret.Code = errorz.CouldNotValidateCode + ret.Message = errorz.CouldNotValidateMessage + } + + } else if causeFieldErr, ok := e.Cause.(*errors.Validation); ok { + //open api validation errors + ret.Cause = &rest_model.APIErrorCause{ + APIFieldError: rest_model.APIFieldError{ + Field: causeFieldErr.Name, + Reason: causeFieldErr.Error(), + Value: fmt.Sprintf("%v", causeFieldErr.Value), + }, + } + ret.Code = errorz.CouldNotValidateCode + ret.Message = errorz.CouldNotValidateMessage + + } else if genericErr, ok := e.Cause.(apierror.GenericCauseError); ok { + ret.Cause = &rest_model.APIErrorCause{ + APIError: rest_model.APIError{ + Data: genericErr.DataMap, + Message: genericErr.Error(), + }, + } + } else { + ret.Cause = &rest_model.APIErrorCause{ + APIError: rest_model.APIError{ + Code: errorz.UnhandledCode, + Message: e.Cause.Error(), + }, + } + } + + } + + return ret +} + func NewResponder(rc *RequestContext) *ResponderImpl { - return &ResponderImpl{rc: rc, producer: runtime.JSONProducer()} + return &ResponderImpl{ + Responder: api.NewResponder(rc, EdgeResponseMapper{}), + } } type ResponderImpl struct { - rc *RequestContext - producer runtime.Producer -} - -func (responder *ResponderImpl) SetProducer(producer runtime.Producer) { - responder.producer = producer -} - -func (responder *ResponderImpl) GetProducer() runtime.Producer { - return responder.producer -} - -func (responder *ResponderImpl) RespondWithCouldNotReadBody(err error) { - responder.RespondWithApiError(apierror.NewCouldNotReadBody(err)) -} - -func (responder *ResponderImpl) RespondWithCouldNotParseBody(err error) { - responder.RespondWithApiError(apierror.NewCouldNotParseBody(err)) -} - -func (responder *ResponderImpl) RespondWithValidationErrors(errors *schema.ValidationErrors) { - responder.RespondWithApiError(errorz.NewCouldNotValidate(errors)) -} - -func (responder *ResponderImpl) RespondWithNotFound() { - responder.RespondWithApiError(errorz.NewNotFound()) -} - -func (responder *ResponderImpl) RespondWithNotFoundWithCause(cause error) { - apiErr := errorz.NewNotFound() - apiErr.Cause = cause - responder.RespondWithApiError(apiErr) -} - -func (responder *ResponderImpl) RespondWithFieldError(fe *errorz.FieldError) { - responder.RespondWithApiError(errorz.NewFieldApiError(errorz.NewFieldError(fe.Reason, fe.FieldName, fe.FieldValue))) + api.Responder } func (responder *ResponderImpl) RespondWithCreatedId(id string, link rest_model.Link) { @@ -103,98 +156,9 @@ func (responder *ResponderImpl) RespondWithCreatedId(id string, link rest_model. responder.Respond(createEnvelope, http.StatusCreated) } -func (responder *ResponderImpl) RespondWithEmptyOk() { - responder.Respond(&rest_model.Empty{Data: map[string]interface{}{}, Meta: &rest_model.Meta{}}, http.StatusOK) -} - func (responder *ResponderImpl) RespondWithOk(data interface{}, meta *rest_model.Meta) { responder.Respond(&rest_model.Empty{ Data: data, Meta: meta, }, http.StatusOK) } - -func (responder *ResponderImpl) Respond(data interface{}, httpStatus int) { - Respond(responder.rc.ResponseWriter, responder.rc.Request.URL.Path, responder.rc.Id, responder.GetProducer(), data, httpStatus) -} - -func (responder *ResponderImpl) RespondWithError(err error) { - RespondWithError(responder.rc.ResponseWriter, responder.rc.Request, responder.rc.Id, responder.GetProducer(), err) -} - -func (responder *ResponderImpl) RespondWithApiError(apiError *errorz.ApiError) { - RespondWithApiError(responder.rc.ResponseWriter, responder.rc.Request, responder.rc.Id, responder.GetProducer(), apiError) -} - -func Respond(w http.ResponseWriter, path, requestId string, producer runtime.Producer, data interface{}, httpStatus int) { - w.WriteHeader(httpStatus) - - err := producer.Produce(w, data) - - if err != nil { - pfxlog.Logger().WithError(err).WithField("requestId", requestId).WithField("path", path).Debug("could not respond, producer errored, possible timeout") - } -} - -func RespondWithError(w http.ResponseWriter, r *http.Request, requestId string, producer runtime.Producer, err error) { - var apiError *errorz.ApiError - var ok bool - - if apiError, ok = err.(*errorz.ApiError); !ok { - apiError = errorz.NewUnhandled(err) - } - - RespondWithApiError(w, r, requestId, producer, apiError) -} - -func RespondWithApiError(w http.ResponseWriter, r *http.Request, requestId string, producer runtime.Producer, apiError *errorz.ApiError) { - data := &rest_model.APIErrorEnvelope{ - Error: apierror.ToRestModel(apiError, requestId), - Meta: &rest_model.Meta{ - APIEnrollmentVersion: version.GetApiEnrollmentVersion(), - APIVersion: version.GetApiVersion(), - }, - } - - if canRespondWithJson(r) { - producer = runtime.JSONProducer() - w.Header().Set("content-type", "application/json") - } - - w.WriteHeader(apiError.Status) - err := producer.Produce(w, data) - - if err != nil { - pfxlog.Logger().WithError(err).WithField("requestId", requestId).Error("could not respond with error, producer errored") - } -} - -func canRespondWithJson(request *http.Request) bool { - //if we can return JSON for errors we should as they provide the most - //information - - canReturnJson := false - - acceptHeaders := request.Header.Values("accept") - if len(acceptHeaders) == 0 { - //no accept == "*/*" - canReturnJson = true - } else { - for _, acceptHeader := range acceptHeaders { //look at all headers values - if canReturnJson { - break - } - - for _, value := range strings.Split(acceptHeader, ",") { //each header can have multiple mimeTypes - mimeType := strings.Split(value, ";")[0] //remove quotients - mimeType = strings.TrimSpace(mimeType) - - if mimeType == "*/*" || mimeType == "application/json" { - canReturnJson = true - break - } - } - } - } - return canReturnJson -} diff --git a/controller/schema/errors.go b/controller/schema/errors.go deleted file mode 100644 index 1e7c8f19b..000000000 --- a/controller/schema/errors.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright NetFoundry, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package schema - -import ( - "encoding/json" - "fmt" - "github.com/xeipuuv/gojsonschema" -) - -type ValidationErrors struct { - Errors []*ValidationError -} - -func (e ValidationErrors) Error() string { - return "schema validation failed" -} - -func (e ValidationErrors) MarshalJSON() ([]byte, error) { - if len(e.Errors) > 1 { - errMap := map[string]interface{}{ - "Reason": "multiple validation errors occurred", - "Errors": e.Errors, - } - return json.Marshal(errMap) - } - - if len(e.Errors) == 1 { - return json.Marshal(e.Errors[0]) - } - - return nil, nil -} - -type ValidationError struct { - Field string `json:"field"` - Type string `json:"type"` - Value interface{} `json:"value"` - Message string `json:"message"` - Details map[string]interface{} `json:"details"` -} - -func (e ValidationError) Error() string { - return fmt.Sprintf("%s is invalid: %s", e.Field, e.Message) -} - -func NewValidationError(err gojsonschema.ResultError) *ValidationError { - return &ValidationError{ - Field: err.Field(), - Type: err.Type(), - Value: err.Value(), - Message: err.String(), - Details: err.Details(), - } -} - -func NewValidationErrors(result *gojsonschema.Result) *ValidationErrors { - var errs []*ValidationError - for _, re := range result.Errors() { - errs = append(errs, NewValidationError(re)) - } - return &ValidationErrors{Errors: errs} -} diff --git a/controller/server/client-api.go b/controller/server/client-api.go index b54663e92..f52b3f40a 100644 --- a/controller/server/client-api.go +++ b/controller/server/client-api.go @@ -22,9 +22,9 @@ import ( "github.com/openziti/edge/controller/apierror" "github.com/openziti/edge/controller/env" "github.com/openziti/edge/controller/response" - "github.com/openziti/edge/controller/timeout" "github.com/openziti/edge/rest_client_api_client" "github.com/openziti/edge/rest_client_api_server" + "github.com/openziti/fabric/controller/api" "github.com/openziti/fabric/xweb" "github.com/pkg/errors" "io/ioutil" @@ -163,7 +163,7 @@ func (clientApi ClientApiHandler) newHandler(ae *env.AppEnv) http.Handler { rc := ae.CreateRequestContext(rw, r) - env.AddRequestContextToHttpContext(r, rc) + api.AddRequestContextToHttpContext(r, rc) err := ae.FillRequestContext(rc) if err != nil { @@ -177,5 +177,5 @@ func (clientApi ClientApiHandler) newHandler(ae *env.AppEnv) http.Handler { innerClientHandler.ServeHTTP(rw, r) }) - return timeout.TimeoutHandler(wrapCorsHandler(handler), 10*time.Second, apierror.NewTimeoutError()) + return api.TimeoutHandler(api.WrapCorsHandler(handler), 10*time.Second, apierror.NewTimeoutError(), response.EdgeResponseMapper{}) } diff --git a/controller/server/controller.go b/controller/server/controller.go index b0baa514d..5f928d17e 100644 --- a/controller/server/controller.go +++ b/controller/server/controller.go @@ -21,6 +21,7 @@ import ( "fmt" sync2 "github.com/openziti/edge/controller/sync_strats" "github.com/openziti/edge/pb/edge_ctrl_pb" + "github.com/openziti/fabric/controller/api_impl" "io/ioutil" "sync" "time" @@ -114,6 +115,8 @@ func NewController(cfg config.Configurable, host env.HostController) (*Controlle panic(err) } + api_impl.OverrideRequestWrapper(&fabricWrapper{ae: c.AppEnv}) + return c, nil } diff --git a/controller/server/cors.go b/controller/server/cors.go deleted file mode 100644 index 1cfbe03c6..000000000 --- a/controller/server/cors.go +++ /dev/null @@ -1,29 +0,0 @@ -package server - -import ( - "github.com/gorilla/handlers" - "github.com/openziti/foundation/common/constants" - "net/http" -) - -func wrapCorsHandler(innerHandler http.Handler) http.Handler{ -corsOpts := []handlers.CORSOption{ - handlers.AllowedOrigins([]string{"*"}), - handlers.OptionStatusCode(200), - handlers.AllowedHeaders([]string{ - "content-type", - "Accept", - constants.ZitiSession, - }), - handlers.AllowedMethods([]string{ - http.MethodGet, - http.MethodHead, - http.MethodPost, - http.MethodPut, - http.MethodPatch, - http.MethodDelete}), - handlers.AllowCredentials(), - } - - return handlers.CORS(corsOpts...)(innerHandler) -} diff --git a/controller/server/fabric-api.go b/controller/server/fabric-api.go new file mode 100644 index 000000000..174fc68db --- /dev/null +++ b/controller/server/fabric-api.go @@ -0,0 +1,78 @@ +package server + +import ( + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/michaelquigley/pfxlog" + "github.com/openziti/edge/controller/env" + "github.com/openziti/edge/controller/internal/permissions" + "github.com/openziti/edge/controller/response" + "github.com/openziti/edge/rest_management_api_server" + "github.com/openziti/fabric/controller/api" + "github.com/openziti/fabric/controller/api_impl" + "github.com/openziti/fabric/controller/apierror" + "github.com/openziti/foundation/util/errorz" + "net/http" + "time" +) + +type fabricWrapper struct { + ae *env.AppEnv +} + +func (self *fabricWrapper) WrapRequest(handler api_impl.RequestHandler, request *http.Request, entityId, entitySubId string) middleware.Responder { + return middleware.ResponderFunc(func(writer http.ResponseWriter, producer runtime.Producer) { + rc, err := env.GetRequestContextFromHttpContext(request) + + if rc == nil { + rc = self.ae.CreateRequestContext(writer, request) + } + + rc.SetProducer(producer) + rc.SetEntityId(entityId) + rc.SetEntitySubId(entitySubId) + + if err != nil { + pfxlog.Logger().WithError(err).Error("could not retrieve request context") + rc.RespondWithError(err) + return + } + + if !permissions.IsAdmin().IsAllowed(rc.ActivePermissions...) { + rc.RespondWithApiError(errorz.NewUnauthorized()) + return + } + + handler(self.ae.GetHostController().GetNetwork(), rc) + }) +} + +func (self *fabricWrapper) WrapHttpHandler(handler http.Handler) http.Handler { + wrapped := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set(ZitiInstanceId, self.ae.InstanceId) + + if r.URL.Path == api_impl.FabricRestApiRootPath { + rw.Header().Set("content-type", "application/json") + rw.WriteHeader(http.StatusOK) + _, _ = rw.Write(rest_management_api_server.SwaggerJSON) + return + } + + rc := self.ae.CreateRequestContext(rw, r) + + api.AddRequestContextToHttpContext(r, rc) + + err := self.ae.FillRequestContext(rc) + if err != nil { + rc.RespondWithError(err) + return + } + + //after request context is filled so that api session is present for session expiration headers + response.AddHeaders(rc) + + handler.ServeHTTP(rw, r) + }) + + return api.TimeoutHandler(api.WrapCorsHandler(wrapped), 10*time.Second, apierror.NewTimeoutError(), response.EdgeResponseMapper{}) +} diff --git a/controller/server/management-api.go b/controller/server/management-api.go index eb221af54..0f4ddd16c 100644 --- a/controller/server/management-api.go +++ b/controller/server/management-api.go @@ -22,9 +22,9 @@ import ( "github.com/openziti/edge/controller/apierror" "github.com/openziti/edge/controller/env" "github.com/openziti/edge/controller/response" - "github.com/openziti/edge/controller/timeout" "github.com/openziti/edge/rest_management_api_client" "github.com/openziti/edge/rest_management_api_server" + "github.com/openziti/fabric/controller/api" "github.com/openziti/fabric/xweb" "net/http" "strings" @@ -119,7 +119,7 @@ func (managementApi ManagementApiHandler) newHandler(ae *env.AppEnv) http.Handle rc := ae.CreateRequestContext(rw, r) - env.AddRequestContextToHttpContext(r, rc) + api.AddRequestContextToHttpContext(r, rc) err := ae.FillRequestContext(rc) if err != nil { @@ -133,5 +133,5 @@ func (managementApi ManagementApiHandler) newHandler(ae *env.AppEnv) http.Handle innerManagementHandler.ServeHTTP(rw, r) }) - return timeout.TimeoutHandler(wrapCorsHandler(handler), 10*time.Second, apierror.NewTimeoutError()) + return api.TimeoutHandler(api.WrapCorsHandler(handler), 10*time.Second, apierror.NewTimeoutError(), response.EdgeResponseMapper{}) } diff --git a/controller/sync_strats/sync_instant.go b/controller/sync_strats/sync_instant.go index 41d1f5053..52b782414 100644 --- a/controller/sync_strats/sync_instant.go +++ b/controller/sync_strats/sync_instant.go @@ -22,12 +22,12 @@ import ( "github.com/golang/protobuf/proto" "github.com/lucsky/cuid" "github.com/michaelquigley/pfxlog" - "github.com/openziti/edge/build" "github.com/openziti/edge/controller/env" "github.com/openziti/edge/controller/handler_edge_ctrl" "github.com/openziti/edge/controller/model" "github.com/openziti/edge/controller/persistence" "github.com/openziti/edge/pb/edge_ctrl_pb" + "github.com/openziti/fabric/build" "github.com/openziti/fabric/controller/network" "github.com/openziti/foundation/channel2" "github.com/openziti/foundation/storage/ast" diff --git a/controller/timeout/timeouts.go b/controller/timeout/timeouts.go deleted file mode 100644 index d88027513..000000000 --- a/controller/timeout/timeouts.go +++ /dev/null @@ -1,178 +0,0 @@ -package timeout - -import ( - "bytes" - "context" - "errors" - "fmt" - "github.com/go-openapi/runtime" - "github.com/michaelquigley/pfxlog" - "github.com/openziti/edge/controller/apierror" - "github.com/openziti/edge/controller/env" - "github.com/openziti/foundation/util/debugz" - "github.com/openziti/foundation/util/errorz" - "net/http" - "path" - goruntime "runtime" - "strings" - "sync" - "time" -) - -func TimeoutHandler(h http.Handler, dt time.Duration, apiErr *errorz.ApiError) http.Handler { - return &timeoutHandler{ - handler: h, - apiError: apiErr, - dt: dt, - producer: runtime.JSONProducer(), - } -} - -type timeoutHandler struct { - handler http.Handler - dt time.Duration - apiError *errorz.ApiError - producer runtime.Producer -} - -func (h *timeoutHandler) errorBody(w http.ResponseWriter, r *http.Request) error { - if h.apiError != nil { - requestId := "" - rc, _ := env.GetRequestContextFromHttpContext(r) - if rc != nil { - requestId = rc.Id - } - apiError := apierror.ToRestModel(h.apiError, requestId) - err := h.producer.Produce(w, apiError) - - return err - } - return errors.New("no timout api error specified") -} - -func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx, cancelCtx := context.WithTimeout(r.Context(), h.dt) - defer cancelCtx() - - r = r.WithContext(ctx) - done := make(chan struct{}) - tw := &timeoutWriter{ - w: w, - h: make(http.Header), - req: r, - } - panicChan := make(chan interface{}, 1) - go func() { - defer func() { - if p := recover(); p != nil { - pfxlog.Logger().Errorf("panic caught by timeout handler: %v\n%v", p, debugz.GenerateLocalStack()) - panicChan <- p - } - }() - h.handler.ServeHTTP(tw, r) - close(done) - }() - select { - case <-panicChan: - case <-done: - tw.mu.Lock() - defer tw.mu.Unlock() - dst := w.Header() - for k, vv := range tw.h { - dst[k] = vv - } - if !tw.wroteHeader { - tw.code = http.StatusOK - } - w.WriteHeader(tw.code) - _, _ = w.Write(tw.wbuf.Bytes()) - case <-ctx.Done(): - tw.mu.Lock() - defer tw.mu.Unlock() - w.Header().Set("content-type", "application/json") - w.WriteHeader(http.StatusServiceUnavailable) - _ = h.errorBody(w, r) - tw.timedOut = true - } -} - -type timeoutWriter struct { - w http.ResponseWriter - h http.Header - wbuf bytes.Buffer - req *http.Request - - mu sync.Mutex - timedOut bool - wroteHeader bool - code int -} - -var _ http.Pusher = (*timeoutWriter)(nil) - -func (tw *timeoutWriter) Push(target string, opts *http.PushOptions) error { - if pusher, ok := tw.w.(http.Pusher); ok { - return pusher.Push(target, opts) - } - return http.ErrNotSupported -} - -func (tw *timeoutWriter) Header() http.Header { return tw.h } - -func (tw *timeoutWriter) Write(p []byte) (int, error) { - tw.mu.Lock() - defer tw.mu.Unlock() - if tw.timedOut { - return 0, http.ErrHandlerTimeout - } - if !tw.wroteHeader { - tw.writeHeaderLocked(http.StatusOK) - } - return tw.wbuf.Write(p) -} - -func (tw *timeoutWriter) writeHeaderLocked(code int) { - checkWriteHeaderCode(code) - - switch { - case tw.timedOut: - return - case tw.wroteHeader: - if tw.req != nil { - caller := relevantCaller() - pfxlog.Logger().Errorf("http: superfluous response.WriteHeader call from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line) - } - default: - tw.wroteHeader = true - tw.code = code - } -} - -func (tw *timeoutWriter) WriteHeader(code int) { - tw.mu.Lock() - defer tw.mu.Unlock() - tw.writeHeaderLocked(code) -} - -func checkWriteHeaderCode(code int) { - if code < 100 || code > 999 { - panic(fmt.Sprintf("invalid WriteHeader code %v", code)) - } -} - -func relevantCaller() goruntime.Frame { - pc := make([]uintptr, 16) - n := goruntime.Callers(1, pc) - frames := goruntime.CallersFrames(pc[:n]) - var frame goruntime.Frame - for { - frame, more := frames.Next() - if !strings.HasPrefix(frame.Function, "net/http.") && !strings.HasPrefix(frame.Function, "github.com/openziti/edge/controller/timeout") { - return frame - } - if !more { - break - } - } - return frame -} diff --git a/go.mod b/go.mod index c71decf29..322b741ea 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,6 @@ require ( github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.6 github.com/google/uuid v1.3.0 - github.com/gorilla/handlers v1.5.1 github.com/jessevdk/go-flags v1.5.0 github.com/jinzhu/copier v0.3.2 github.com/kataras/go-events v0.0.3-0.20201007151548-c411dc70c0a6 @@ -88,6 +87,7 @@ require ( github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/gorilla/handlers v1.5.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/router/handler_edge_ctrl/hello.go b/router/handler_edge_ctrl/hello.go index aa72e9f2e..b3a3856a1 100644 --- a/router/handler_edge_ctrl/hello.go +++ b/router/handler_edge_ctrl/hello.go @@ -19,9 +19,9 @@ package handler_edge_ctrl import ( "github.com/golang/protobuf/proto" "github.com/michaelquigley/pfxlog" - "github.com/openziti/edge/build" "github.com/openziti/edge/controller/env" "github.com/openziti/edge/pb/edge_ctrl_pb" + "github.com/openziti/fabric/build" "github.com/openziti/foundation/channel2" ) diff --git a/router/xgress_edge_tunnel/fabric.go b/router/xgress_edge_tunnel/fabric.go index 7e3b826b7..db02ec490 100644 --- a/router/xgress_edge_tunnel/fabric.go +++ b/router/xgress_edge_tunnel/fabric.go @@ -22,10 +22,10 @@ import ( "github.com/google/uuid" "github.com/michaelquigley/pfxlog" "github.com/netfoundry/secretstream/kx" - "github.com/openziti/edge/build" "github.com/openziti/edge/pb/edge_ctrl_pb" "github.com/openziti/edge/router/xgress_common" "github.com/openziti/edge/tunnel" + "github.com/openziti/fabric/build" "github.com/openziti/fabric/router/xgress" "github.com/openziti/foundation/channel2" "github.com/openziti/foundation/identity/identity" diff --git a/version b/version index 9f4eca259..5320adc1c 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.20 +0.21