mirror of
https://github.com/openziti/ziti.git
synced 2026-10-13 15:51:21 +00:00
Initial pass at fabric rest api working (#838)
* Update for fabric rest controller changes
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Vendored
+14
-22
@@ -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())
|
||||
|
||||
Vendored
+30
@@ -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
|
||||
}
|
||||
Vendored
+5
-21
@@ -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)
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+107
-143
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user