diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/authinfo_test.go golang-github-go-openapi-runtime-0.15.0/authinfo_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/authinfo_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/authinfo_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -23,11 +23,11 @@ func TestAuthInfoWriter(t *testing.T) { hand := ClientAuthInfoWriterFunc(func(r ClientRequest, _ strfmt.Registry) error { - r.SetHeaderParam("authorization", "Bearer the-token-goes-here") - return nil + return r.SetHeaderParam("authorization", "Bearer the-token-goes-here") }) tr := new(trw) - hand.AuthenticateRequest(tr, nil) + err := hand.AuthenticateRequest(tr, nil) + assert.NoError(t, err) assert.Equal(t, "Bearer the-token-goes-here", tr.Headers.Get("Authorization")) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/bytestream.go golang-github-go-openapi-runtime-0.15.0/bytestream.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/bytestream.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/bytestream.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,31 +15,137 @@ package runtime import ( + "bytes" + "encoding" "errors" + "fmt" "io" + "reflect" + + "github.com/go-openapi/swag" ) -// ByteStreamConsumer creates a consmer for byte streams, takes a writer and reads from the provided reader -func ByteStreamConsumer() Consumer { - return ConsumerFunc(func(r io.Reader, v interface{}) error { - wrtr, ok := v.(io.Writer) - if !ok { - return errors.New("ByteStreamConsumer can only deal with io.Writer") +func defaultCloser() error { return nil } + +type byteStreamOpt func(opts *byteStreamOpts) + +// ClosesStream when the bytestream consumer or producer is finished +func ClosesStream(opts *byteStreamOpts) { + opts.Close = true +} + +type byteStreamOpts struct { + Close bool +} + +// ByteStreamConsumer creates a consmer for byte streams, +// takes a Writer/BinaryUnmarshaler interface or binary slice by reference, +// and reads from the provided reader +func ByteStreamConsumer(opts ...byteStreamOpt) Consumer { + var vals byteStreamOpts + for _, opt := range opts { + opt(&vals) + } + + return ConsumerFunc(func(reader io.Reader, data interface{}) error { + if reader == nil { + return errors.New("ByteStreamConsumer requires a reader") // early exit + } + + close := defaultCloser + if vals.Close { + if cl, ok := reader.(io.Closer); ok { + close = cl.Close + } + } + defer close() + + if wrtr, ok := data.(io.Writer); ok { + _, err := io.Copy(wrtr, reader) + return err + } + + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(reader) + if err != nil { + return err } + b := buf.Bytes() - _, err := io.Copy(wrtr, r) - return err + if bu, ok := data.(encoding.BinaryUnmarshaler); ok { + return bu.UnmarshalBinary(b) + } + + if t := reflect.TypeOf(data); data != nil && t.Kind() == reflect.Ptr { + v := reflect.Indirect(reflect.ValueOf(data)) + if t = v.Type(); t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 { + v.SetBytes(b) + return nil + } + } + + return fmt.Errorf("%v (%T) is not supported by the ByteStreamConsumer, %s", + data, data, "can be resolved by supporting Writer/BinaryUnmarshaler interface") }) } -// ByteStreamProducer creates a producer for byte streams, takes a reader, writes to a writer (essentially a pipe) -func ByteStreamProducer() Producer { - return ProducerFunc(func(w io.Writer, v interface{}) error { - rdr, ok := v.(io.Reader) - if !ok { - return errors.New("ByteStreamProducer can only deal with io.Reader") +// ByteStreamProducer creates a producer for byte streams, +// takes a Reader/BinaryMarshaler interface or binary slice, +// and writes to a writer (essentially a pipe) +func ByteStreamProducer(opts ...byteStreamOpt) Producer { + var vals byteStreamOpts + for _, opt := range opts { + opt(&vals) + } + return ProducerFunc(func(writer io.Writer, data interface{}) error { + if writer == nil { + return errors.New("ByteStreamProducer requires a writer") // early exit + } + close := defaultCloser + if vals.Close { + if cl, ok := writer.(io.Closer); ok { + close = cl.Close + } + } + defer close() + + if rdr, ok := data.(io.Reader); ok { + _, err := io.Copy(writer, rdr) + return err } - _, err := io.Copy(w, rdr) - return err + + if bm, ok := data.(encoding.BinaryMarshaler); ok { + bytes, err := bm.MarshalBinary() + if err != nil { + return err + } + + _, err = writer.Write(bytes) + return err + } + + if data != nil { + if e, ok := data.(error); ok { + _, err := writer.Write([]byte(e.Error())) + return err + } + + v := reflect.Indirect(reflect.ValueOf(data)) + if t := v.Type(); t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 { + _, err := writer.Write(v.Bytes()) + return err + } + if t := v.Type(); t.Kind() == reflect.Struct || t.Kind() == reflect.Slice { + b, err := swag.WriteJSON(data) + if err != nil { + return err + } + _, err = writer.Write(b) + return err + } + } + + return fmt.Errorf("%v (%T) is not supported by the ByteStreamProducer, %s", + data, data, "can be resolved by supporting Reader/BinaryMarshaler interface") }) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/bytestream_test.go golang-github-go-openapi-runtime-0.15.0/bytestream_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/bytestream_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/bytestream_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -2,6 +2,9 @@ import ( "bytes" + "errors" + "fmt" + "sync/atomic" "testing" "github.com/stretchr/testify/assert" @@ -9,22 +12,209 @@ func TestByteStreamConsumer(t *testing.T) { cons := ByteStreamConsumer() + expected := "the data for the stream to be sent over the wire" - rdr := bytes.NewBufferString(expected) - var in bytes.Buffer - if assert.NoError(t, cons.Consume(rdr, &in)) { - assert.Equal(t, expected, in.String()) + // can consume as a Writer + var b bytes.Buffer + if assert.NoError(t, cons.Consume(bytes.NewBufferString(expected), &b)) { + assert.Equal(t, expected, b.String()) + } + + // can consume as an UnmarshalBinary + var bu binaryUnmarshalDummy + if assert.NoError(t, cons.Consume(bytes.NewBufferString(expected), &bu)) { + assert.Equal(t, expected, bu.str) + } + + // can consume as a binary slice + var bs []byte + if assert.NoError(t, cons.Consume(bytes.NewBufferString(expected), &bs)) { + assert.Equal(t, expected, string(bs)) + } + type binarySlice []byte + var bs2 binarySlice + if assert.NoError(t, cons.Consume(bytes.NewBufferString(expected), &bs2)) { + assert.Equal(t, expected, string(bs2)) + } + + // passing in a nilslice wil result in an error + var ns *[]byte + assert.Error(t, cons.Consume(bytes.NewBufferString(expected), &ns)) + + // passing in nil wil result in an error as well + assert.Error(t, cons.Consume(bytes.NewBufferString(expected), nil)) + + // a reader who results in an error, will make it fail + assert.Error(t, cons.Consume(new(nopReader), &bu)) + assert.Error(t, cons.Consume(new(nopReader), &bs)) + + // the readers can also not be nil + assert.Error(t, cons.Consume(nil, &bs)) +} + +type binaryUnmarshalDummy struct { + str string +} + +func (b *binaryUnmarshalDummy) UnmarshalBinary(bytes []byte) error { + if len(bytes) == 0 { + return errors.New("no text given") } + + b.str = string(bytes) + return nil } func TestByteStreamProducer(t *testing.T) { cons := ByteStreamProducer() - var wrtr bytes.Buffer expected := "the data for the stream to be sent over the wire" - out := bytes.NewBufferString(expected) - if assert.NoError(t, cons.Produce(&wrtr, out)) { - assert.Equal(t, expected, wrtr.String()) + var rdr bytes.Buffer + + // can produce using a reader + if assert.NoError(t, cons.Produce(&rdr, bytes.NewBufferString(expected))) { + assert.Equal(t, expected, rdr.String()) + rdr.Reset() + } + + // can produce using a binary marshaller + if assert.NoError(t, cons.Produce(&rdr, &binaryMarshalDummy{expected})) { + assert.Equal(t, expected, rdr.String()) + rdr.Reset() + } + + // binary slices can also be used to produce + if assert.NoError(t, cons.Produce(&rdr, []byte(expected))) { + assert.Equal(t, expected, rdr.String()) + rdr.Reset() + } + + // errors can also be used to produce + if assert.NoError(t, cons.Produce(&rdr, errors.New(expected))) { + assert.Equal(t, expected, rdr.String()) + rdr.Reset() + } + + // structs can also be used to produce + if assert.NoError(t, cons.Produce(&rdr, Error{Message: expected})) { + assert.Equal(t, fmt.Sprintf(`{"message":%q}`, expected), rdr.String()) + rdr.Reset() + } + + // struct pointers can also be used to produce + if assert.NoError(t, cons.Produce(&rdr, &Error{Message: expected})) { + assert.Equal(t, fmt.Sprintf(`{"message":%q}`, expected), rdr.String()) + rdr.Reset() + } + + // slices can also be used to produce + if assert.NoError(t, cons.Produce(&rdr, []string{expected})) { + assert.Equal(t, fmt.Sprintf(`[%q]`, expected), rdr.String()) + rdr.Reset() + } + + type binarySlice []byte + if assert.NoError(t, cons.Produce(&rdr, binarySlice(expected))) { + assert.Equal(t, expected, rdr.String()) + rdr.Reset() + } + + // when binaryMarshal data is used, its potential error gets propagated + assert.Error(t, cons.Produce(&rdr, new(binaryMarshalDummy))) + // nil data should never be accepted either + assert.Error(t, cons.Produce(&rdr, nil)) + // nil readers should also never be acccepted + assert.Error(t, cons.Produce(nil, bytes.NewBufferString(expected))) +} + +type binaryMarshalDummy struct { + str string +} + +func (b *binaryMarshalDummy) MarshalBinary() ([]byte, error) { + if len(b.str) == 0 { + return nil, errors.New("no text set") + } + + return []byte(b.str), nil +} + +type closingWriter struct { + calledClose int64 + calledWrite int64 + b bytes.Buffer +} + +func (c *closingWriter) Close() error { + atomic.AddInt64(&c.calledClose, 1) + return nil +} + +func (c *closingWriter) Write(p []byte) (n int, err error) { + atomic.AddInt64(&c.calledWrite, 1) + return c.b.Write(p) +} + +func (c *closingWriter) String() string { + return c.b.String() +} + +type closingReader struct { + calledClose int64 + calledRead int64 + b *bytes.Buffer +} + +func (c *closingReader) Close() error { + atomic.AddInt64(&c.calledClose, 1) + return nil +} + +func (c *closingReader) Read(p []byte) (n int, err error) { + atomic.AddInt64(&c.calledRead, 1) + return c.b.Read(p) +} + +func TestBytestreamConsumer_Close(t *testing.T) { + cons := ByteStreamConsumer(ClosesStream) + expected := "the data for the stream to be sent over the wire" + + // can consume as a Writer + var b bytes.Buffer + r := &closingReader{b: bytes.NewBufferString(expected)} + if assert.NoError(t, cons.Consume(r, &b)) { + assert.Equal(t, expected, b.String()) + assert.EqualValues(t, 1, r.calledClose) + } + + // can consume as a Writer + cons = ByteStreamConsumer() + b.Reset() + r = &closingReader{b: bytes.NewBufferString(expected)} + if assert.NoError(t, cons.Consume(r, &b)) { + assert.Equal(t, expected, b.String()) + assert.EqualValues(t, 0, r.calledClose) + } +} + +func TestBytestreamProducer_Close(t *testing.T) { + cons := ByteStreamProducer(ClosesStream) + expected := "the data for the stream to be sent over the wire" + + // can consume as a Writer + r := &closingWriter{} + // can produce using a reader + if assert.NoError(t, cons.Produce(r, bytes.NewBufferString(expected))) { + assert.Equal(t, expected, r.String()) + assert.EqualValues(t, 1, r.calledClose) + } + + cons = ByteStreamProducer() + r = &closingWriter{} + // can produce using a reader + if assert.NoError(t, cons.Produce(r, bytes.NewBufferString(expected))) { + assert.Equal(t, expected, r.String()) + assert.EqualValues(t, 0, r.calledClose) } } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/auth_info.go golang-github-go-openapi-runtime-0.15.0/client/auth_info.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/auth_info.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client/auth_info.go 2018-06-28 22:01:56.000000000 +0000 @@ -21,12 +21,18 @@ "github.com/go-openapi/strfmt" ) +// PassThroughAuth never manipulates the request +var PassThroughAuth runtime.ClientAuthInfoWriter + +func init() { + PassThroughAuth = runtime.ClientAuthInfoWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error { return nil }) +} + // BasicAuth provides a basic auth info writer func BasicAuth(username, password string) runtime.ClientAuthInfoWriter { return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { encoded := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) - r.SetHeaderParam("Authorization", "Basic "+encoded) - return nil + return r.SetHeaderParam("Authorization", "Basic "+encoded) }) } @@ -34,15 +40,13 @@ func APIKeyAuth(name, in, value string) runtime.ClientAuthInfoWriter { if in == "query" { return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { - r.SetQueryParam(name, value) - return nil + return r.SetQueryParam(name, value) }) } if in == "header" { return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { - r.SetHeaderParam(name, value) - return nil + return r.SetHeaderParam(name, value) }) } return nil @@ -51,7 +55,6 @@ // BearerToken provides a header based oauth2 bearer access token auth info writer func BearerToken(token string) runtime.ClientAuthInfoWriter { return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { - r.SetHeaderParam("Authorization", "Bearer "+token) - return nil + return r.SetHeaderParam("Authorization", "Bearer "+token) }) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/auth_info_test.go golang-github-go-openapi-runtime-0.15.0/client/auth_info_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/auth_info_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client/auth_info_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -25,7 +25,8 @@ r, _ := newRequest("GET", "/", nil) writer := BasicAuth("someone", "with a password") - writer.AuthenticateRequest(r, nil) + err := writer.AuthenticateRequest(r, nil) + assert.NoError(t, err) req := new(http.Request) req.Header = make(http.Header) @@ -41,7 +42,8 @@ r, _ := newRequest("GET", "/", nil) writer := APIKeyAuth("api_key", "query", "the-shared-key") - writer.AuthenticateRequest(r, nil) + err := writer.AuthenticateRequest(r, nil) + assert.NoError(t, err) assert.Equal(t, "the-shared-key", r.query.Get("api_key")) } @@ -50,7 +52,8 @@ r, _ := newRequest("GET", "/", nil) writer := APIKeyAuth("x-api-token", "header", "the-shared-key") - writer.AuthenticateRequest(r, nil) + err := writer.AuthenticateRequest(r, nil) + assert.NoError(t, err) assert.Equal(t, "the-shared-key", r.header.Get("x-api-token")) } @@ -59,7 +62,8 @@ r, _ := newRequest("GET", "/", nil) writer := BearerToken("the-shared-token") - writer.AuthenticateRequest(r, nil) + err := writer.AuthenticateRequest(r, nil) + assert.NoError(t, err) assert.Equal(t, "Bearer the-shared-token", r.header.Get("Authorization")) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/keepalive.go golang-github-go-openapi-runtime-0.15.0/client/keepalive.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/keepalive.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client/keepalive.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,53 @@ +package client + +import ( + "io" + "io/ioutil" + "net/http" + "sync/atomic" +) + +// KeepAliveTransport drains the remaining body from a response +// so that go will reuse the TCP connections. +// This is not enabled by default because there are servers where +// the response never gets closed and that would make the code hang forever. +// So instead it's provided as a http client middleware that can be used to override +// any request. +func KeepAliveTransport(rt http.RoundTripper) http.RoundTripper { + return &keepAliveTransport{wrapped: rt} +} + +type keepAliveTransport struct { + wrapped http.RoundTripper +} + +func (k *keepAliveTransport) RoundTrip(r *http.Request) (*http.Response, error) { + resp, err := k.wrapped.RoundTrip(r) + if err != nil { + return resp, err + } + resp.Body = &drainingReadCloser{rdr: resp.Body} + return resp, nil +} + +type drainingReadCloser struct { + rdr io.ReadCloser + seenEOF uint32 +} + +func (d *drainingReadCloser) Read(p []byte) (n int, err error) { + n, err = d.rdr.Read(p) + if err == io.EOF || n == 0 { + atomic.StoreUint32(&d.seenEOF, 1) + } + return +} + +func (d *drainingReadCloser) Close() error { + // drain buffer + if atomic.LoadUint32(&d.seenEOF) != 1 { + //#nosec + io.Copy(ioutil.Discard, d.rdr) + } + return d.rdr.Close() +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/keepalive_test.go golang-github-go-openapi-runtime-0.15.0/client/keepalive_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/keepalive_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client/keepalive_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,73 @@ +package client + +import ( + "bytes" + "io" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" +) + +func newCountingReader(rdr io.Reader, readOnce bool) *countingReadCloser { + return &countingReadCloser{ + rdr: rdr, + readOnce: readOnce, + } +} + +type countingReadCloser struct { + rdr io.Reader + readOnce bool + readCalled int + closeCalled int +} + +func (c *countingReadCloser) Read(b []byte) (int, error) { + c.readCalled++ + if c.readCalled > 1 && c.readOnce { + return 0, io.EOF + } + return c.rdr.Read(b) +} + +func (c *countingReadCloser) Close() error { + c.closeCalled++ + return nil +} + +func TestDrainingReadCloser(t *testing.T) { + rdr := newCountingReader(bytes.NewBufferString("There are many things to do"), false) + prevDisc := ioutil.Discard + disc := bytes.NewBuffer(nil) + ioutil.Discard = disc + defer func() { ioutil.Discard = prevDisc }() + + buf := make([]byte, 5) + ts := &drainingReadCloser{rdr: rdr} + ts.Read(buf) + ts.Close() + assert.Equal(t, "There", string(buf)) + assert.Equal(t, " are many things to do", disc.String()) + assert.Equal(t, 3, rdr.readCalled) + assert.Equal(t, 1, rdr.closeCalled) +} + +func TestDrainingReadCloser_SeenEOF(t *testing.T) { + rdr := newCountingReader(bytes.NewBufferString("There are many things to do"), true) + prevDisc := ioutil.Discard + disc := bytes.NewBuffer(nil) + ioutil.Discard = disc + defer func() { ioutil.Discard = prevDisc }() + + buf := make([]byte, 5) + ts := &drainingReadCloser{rdr: rdr} + ts.Read(buf) + _, err := ts.Read(nil) + assert.Equal(t, io.EOF, err) + ts.Close() + assert.Equal(t, string(buf), "There") + assert.Equal(t, disc.String(), "") + assert.Equal(t, 2, rdr.readCalled) + assert.Equal(t, 1, rdr.closeCalled) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/request.go golang-github-go-openapi-runtime-0.15.0/client/request.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/request.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client/request.go 2018-06-28 22:01:56.000000000 +0000 @@ -24,6 +24,7 @@ "net/http" "net/url" "os" + "path" "path/filepath" "strings" "time" @@ -62,9 +63,10 @@ header http.Header query url.Values formFields url.Values - fileFields map[string]*os.File + fileFields map[string][]runtime.NamedReadCloser payload interface{} timeout time.Duration + buf *bytes.Buffer } var ( @@ -72,83 +74,119 @@ _ runtime.ClientRequest = new(request) ) +func (r *request) isMultipart(mediaType string) bool { + if len(r.fileFields) > 0 { + return true + } + + return runtime.MultipartFormMime == mediaType +} + // BuildHTTP creates a new http request based on the data from the params -func (r *request) BuildHTTP(mediaType string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) { +func (r *request) BuildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) { + return r.buildHTTP(mediaType, basePath, producers, registry, nil) +} + +func (r *request) buildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry, auth runtime.ClientAuthInfoWriter) (*http.Request, error) { // build the data if err := r.writer.WriteToRequest(r, registry); err != nil { return nil, err } + if auth != nil { + if err := auth.AuthenticateRequest(r, registry); err != nil { + return nil, err + } + } + // create http request - path := r.pathPattern + var reinstateSlash bool + if r.pathPattern != "" && r.pathPattern != "/" && r.pathPattern[len(r.pathPattern)-1] == '/' { + reinstateSlash = true + } + urlPath := path.Join(basePath, r.pathPattern) for k, v := range r.pathParams { - path = strings.Replace(path, "{"+k+"}", v, -1) + urlPath = strings.Replace(urlPath, "{"+k+"}", url.PathEscape(v), -1) + } + if reinstateSlash { + urlPath = urlPath + "/" } var body io.ReadCloser var pr *io.PipeReader var pw *io.PipeWriter - buf := bytes.NewBuffer(nil) - body = ioutil.NopCloser(buf) - if r.fileFields != nil { - pr, pw = io.Pipe() - body = pr + + r.buf = bytes.NewBuffer(nil) + if r.payload != nil || len(r.formFields) > 0 || len(r.fileFields) > 0 { + body = ioutil.NopCloser(r.buf) + if r.isMultipart(mediaType) { + pr, pw = io.Pipe() + body = pr + } } - req, err := http.NewRequest(r.method, path, body) + req, err := http.NewRequest(r.method, urlPath, body) + if err != nil { return nil, err } + req.URL.RawQuery = r.query.Encode() req.Header = r.header // check if this is a form type request if len(r.formFields) > 0 || len(r.fileFields) > 0 { - // check if this is multipart - if len(r.fileFields) > 0 { - mp := multipart.NewWriter(pw) - req.Header.Set(runtime.HeaderContentType, mp.FormDataContentType()) - - go func() { - defer func() { - mp.Close() - pw.Close() - }() - - for fn, v := range r.formFields { - if len(v) > 0 { - if err := mp.WriteField(fn, v[0]); err != nil { - pw.CloseWithError(err) - log.Fatal(err) - } + if !r.isMultipart(mediaType) { + req.Header.Set(runtime.HeaderContentType, mediaType) + formString := r.formFields.Encode() + // set content length before writing to the buffer + req.ContentLength = int64(len(formString)) + // write the form values as the body + r.buf.WriteString(formString) + return req, nil + } + + mp := multipart.NewWriter(pw) + req.Header.Set(runtime.HeaderContentType, mangleContentType(mediaType, mp.Boundary())) + + go func() { + defer func() { + mp.Close() + pw.Close() + }() + + for fn, v := range r.formFields { + for _, vi := range v { + if err := mp.WriteField(fn, vi); err != nil { + pw.CloseWithError(err) + log.Println(err) } } + } - for fn, f := range r.fileFields { - wrtr, err := mp.CreateFormFile(fn, filepath.Base(f.Name())) + defer func() { + for _, ff := range r.fileFields { + for _, ffi := range ff { + ffi.Close() + } + } + }() + for fn, f := range r.fileFields { + for _, fi := range f { + wrtr, err := mp.CreateFormFile(fn, filepath.Base(fi.Name())) if err != nil { pw.CloseWithError(err) - log.Fatal(err) + log.Println(err) } - defer func() { - for _, ff := range r.fileFields { - ff.Close() - } - - }() - if _, err := io.Copy(wrtr, f); err != nil { + if _, err := io.Copy(wrtr, fi); err != nil { pw.CloseWithError(err) - log.Fatal(err) + log.Println(err) } } + } + + }() + return req, nil - }() - return req, nil - } else { - req.Header.Set(runtime.HeaderContentType, mediaType) - // write the form values as the body - buf.WriteString(r.formFields.Encode()) - return req, nil - } } // if there is payload, use the producer to write the payload, and then @@ -159,14 +197,29 @@ req.Header.Set(runtime.HeaderContentType, mediaType) if rdr, ok := r.payload.(io.ReadCloser); ok { req.Body = rdr + return req, nil } if rdr, ok := r.payload.(io.Reader); ok { req.Body = ioutil.NopCloser(rdr) + return req, nil } + req.GetBody = func() (io.ReadCloser, error) { + var b bytes.Buffer + producer := producers[mediaType] + if err := producer.Produce(&b, r.payload); err != nil { + return nil, err + } + + if _, err := r.buf.Write(b.Bytes()); err != nil { + return nil, err + } + return ioutil.NopCloser(&b), nil + } + // set the content length of the request or else a chunked transfer is // declared, and this corrupts outgoing JSON payloads. the content's // length must be set prior to the body being written per the spec at @@ -185,13 +238,44 @@ return nil, err } req.ContentLength = int64(b.Len()) - if _, err := buf.Write(b.Bytes()); err != nil { + if _, err := r.buf.Write(b.Bytes()); err != nil { return nil, err } } + + if runtime.CanHaveBody(req.Method) && req.Body == nil && req.Header.Get(runtime.HeaderContentType) == "" { + req.Header.Set(runtime.HeaderContentType, mediaType) + } + return req, nil } +func mangleContentType(mediaType, boundary string) string { + if strings.ToLower(mediaType) == runtime.URLencodedFormMime { + return fmt.Sprintf("%s; boundary=%s", mediaType, boundary) + } + return "multipart/form-data; boundary=" + boundary +} + +func (r *request) GetMethod() string { + return r.method +} + +func (r *request) GetPath() string { + path := r.pathPattern + for k, v := range r.pathParams { + path = strings.Replace(path, "{"+k+"}", v, -1) + } + return path +} + +func (r *request) GetBody() []byte { + if r.buf == nil { + return nil + } + return r.buf.Bytes() +} + // SetHeaderParam adds a header param to the request // when there is only 1 value provided for the varargs, it will set it. // when there are several values provided for the varargs it will add it (no overriding) @@ -207,13 +291,22 @@ // when there is only 1 value provided for the varargs, it will set it. // when there are several values provided for the varargs it will add it (no overriding) func (r *request) SetQueryParam(name string, values ...string) error { - if r.header == nil { + if r.query == nil { r.query = make(url.Values) } r.query[name] = values return nil } +// GetQueryParams returns a copy of all query params currently set for the request +func (r *request) GetQueryParams() url.Values { + var result = make(url.Values) + for key, value := range r.query { + result[key] = append([]string{}, value...) + } + return result +} + // SetFormParam adds a forn param to the request // when there is only 1 value provided for the varargs, it will set it. // when there are several values provided for the varargs it will add it (no overriding) @@ -236,23 +329,27 @@ } // SetFileParam adds a file param to the request -func (r *request) SetFileParam(name string, file *os.File) error { - fi, err := os.Stat(file.Name()) - if err != nil { - return err - } - if fi.IsDir() { - return fmt.Errorf("%q is a directory, only files are supported", file.Name()) +func (r *request) SetFileParam(name string, files ...runtime.NamedReadCloser) error { + for _, file := range files { + if actualFile, ok := file.(*os.File); ok { + fi, err := os.Stat(actualFile.Name()) + if err != nil { + return err + } + if fi.IsDir() { + return fmt.Errorf("%q is a directory, only files are supported", file.Name()) + } + } } if r.fileFields == nil { - r.fileFields = make(map[string]*os.File) + r.fileFields = make(map[string][]runtime.NamedReadCloser) } if r.formFields == nil { r.formFields = make(url.Values) } - r.fileFields[name] = file + r.fileFields[name] = files return nil } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/request_test.go golang-github-go-openapi-runtime-0.15.0/client/request_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/request_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client/request_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,6 +15,7 @@ package client import ( + "bytes" "encoding/json" "encoding/xml" "io/ioutil" @@ -22,6 +23,7 @@ "mime/multipart" "os" "path/filepath" + "strings" "testing" "github.com/go-openapi/runtime" @@ -38,20 +40,20 @@ func TestBuildRequest_SetHeaders(t *testing.T) { r, _ := newRequest("GET", "/flats/{id}/", nil) // single value - r.SetHeaderParam("X-Rate-Limit", "500") + _ = r.SetHeaderParam("X-Rate-Limit", "500") assert.Equal(t, "500", r.header.Get("X-Rate-Limit")) - r.SetHeaderParam("X-Rate-Limit", "400") + _ = r.SetHeaderParam("X-Rate-Limit", "400") assert.Equal(t, "400", r.header.Get("X-Rate-Limit")) // multi value - r.SetHeaderParam("X-Accepts", "json", "xml", "yaml") + _ = r.SetHeaderParam("X-Accepts", "json", "xml", "yaml") assert.EqualValues(t, []string{"json", "xml", "yaml"}, r.header["X-Accepts"]) } func TestBuildRequest_SetPath(t *testing.T) { r, _ := newRequest("GET", "/flats/{id}/?hello=world", nil) - r.SetPathParam("id", "1345") + _ = r.SetPathParam("id", "1345") assert.Equal(t, "1345", r.pathParams["id"]) } @@ -59,20 +61,20 @@ r, _ := newRequest("GET", "/flats/{id}/", nil) // single value - r.SetQueryParam("hello", "there") + _ = r.SetQueryParam("hello", "there") assert.Equal(t, "there", r.query.Get("hello")) // multi value - r.SetQueryParam("goodbye", "cruel", "world") + _ = r.SetQueryParam("goodbye", "cruel", "world") assert.Equal(t, []string{"cruel", "world"}, r.query["goodbye"]) } func TestBuildRequest_SetForm(t *testing.T) { // non-multipart r, _ := newRequest("POST", "/flats", nil) - r.SetFormParam("hello", "world") + _ = r.SetFormParam("hello", "world") assert.Equal(t, "world", r.formFields.Get("hello")) - r.SetFormParam("goodbye", "cruel", "world") + _ = r.SetFormParam("goodbye", "cruel", "world") assert.Equal(t, []string{"cruel", "world"}, r.formFields["goodbye"]) } @@ -90,7 +92,16 @@ if assert.NoError(t, err) { fl, ok := r.fileFields["file"] if assert.True(t, ok) { - assert.Equal(t, "runtime.go", filepath.Base(fl.Name())) + assert.Equal(t, "runtime.go", filepath.Base(fl[0].Name())) + } + } + // success adds a file param with multiple files + err = r.SetFileParam("otherfiles", mustGetFile("./runtime.go"), mustGetFile("./request.go")) + if assert.NoError(t, err) { + fl, ok := r.fileFields["otherfiles"] + if assert.True(t, ok) { + assert.Equal(t, "runtime.go", filepath.Base(fl[0].Name())) + assert.Equal(t, "request.go", filepath.Base(fl[1].Name())) } } } @@ -107,23 +118,74 @@ r, _ := newRequest("GET", "/flats/{id}/?hello=world", nil) bd := []struct{ Name, Hobby string }{{"Tom", "Organ trail"}, {"John", "Bird watching"}} - r.SetBodyParam(bd) + _ = r.SetBodyParam(bd) assert.Equal(t, bd, r.payload) } +func TestBuildRequest_BuildHTTP_NoPayload(t *testing.T) { + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetBodyParam(nil) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + r, _ := newRequest("POST", "/flats/{id}/", reqWrtr) + + req, err := r.BuildHTTP(runtime.JSONMime, "", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/flats/1234/", req.URL.Path) + assert.Equal(t, runtime.JSONMime, req.Header.Get(runtime.HeaderContentType)) + } +} + func TestBuildRequest_BuildHTTP_Payload(t *testing.T) { bd := []struct{ Name, Hobby string }{{"Tom", "Organ trail"}, {"John", "Bird watching"}} reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { - req.SetBodyParam(bd) - req.SetQueryParam("hello", "world") - req.SetPathParam("id", "1234") - req.SetHeaderParam("X-Rate-Limit", "200") + _ = req.SetBodyParam(bd) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") return nil }) r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) - r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime) - req, err := r.BuildHTTP(runtime.JSONMime, testProducers, nil) + req, err := r.BuildHTTP(runtime.JSONMime, "", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/flats/1234/", req.URL.Path) + expectedBody, _ := json.Marshal(bd) + actualBody, _ := ioutil.ReadAll(req.Body) + assert.Equal(t, append(expectedBody, '\n'), actualBody) + } +} + +func TestBuildRequest_BuildHTTP_SetsInAuth(t *testing.T) { + bd := []struct{ Name, Hobby string }{{"Tom", "Organ trail"}, {"John", "Bird watching"}} + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetBodyParam(bd) + _ = req.SetQueryParam("hello", "wrong") + _ = req.SetPathParam("id", "wrong") + _ = req.SetHeaderParam("X-Rate-Limit", "wrong") + return nil + }) + + auth := runtime.ClientAuthInfoWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetBodyParam(bd) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + + r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime) + + req, err := r.buildHTTP(runtime.JSONMime, "", testProducers, nil, auth) if assert.NoError(t, err) && assert.NotNil(t, req) { assert.Equal(t, "200", req.Header.Get("x-rate-limit")) assert.Equal(t, "world", req.URL.Query().Get("hello")) @@ -141,16 +203,16 @@ Hobby string `xml:"hobby"` }{{xml.Name{}, "Tom", "Organ trail"}, {xml.Name{}, "John", "Bird watching"}} reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { - req.SetBodyParam(bd) - req.SetQueryParam("hello", "world") - req.SetPathParam("id", "1234") - req.SetHeaderParam("X-Rate-Limit", "200") + _ = req.SetBodyParam(bd) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") return nil }) r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) - r.SetHeaderParam(runtime.HeaderContentType, runtime.XMLMime) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.XMLMime) - req, err := r.BuildHTTP(runtime.XMLMime, testProducers, nil) + req, err := r.BuildHTTP(runtime.XMLMime, "", testProducers, nil) if assert.NoError(t, err) && assert.NotNil(t, req) { assert.Equal(t, "200", req.Header.Get("x-rate-limit")) assert.Equal(t, "world", req.URL.Query().Get("hello")) @@ -164,16 +226,16 @@ func TestBuildRequest_BuildHTTP_TextPayload(t *testing.T) { bd := "Tom: Organ trail; John: Bird watching" reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { - req.SetBodyParam(bd) - req.SetQueryParam("hello", "world") - req.SetPathParam("id", "1234") - req.SetHeaderParam("X-Rate-Limit", "200") + _ = req.SetBodyParam(bd) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") return nil }) r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) - r.SetHeaderParam(runtime.HeaderContentType, runtime.TextMime) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.TextMime) - req, err := r.BuildHTTP(runtime.TextMime, testProducers, nil) + req, err := r.BuildHTTP(runtime.TextMime, "", testProducers, nil) if assert.NoError(t, err) && assert.NotNil(t, req) { assert.Equal(t, "200", req.Header.Get("x-rate-limit")) assert.Equal(t, "world", req.URL.Query().Get("hello")) @@ -186,16 +248,16 @@ func TestBuildRequest_BuildHTTP_Form(t *testing.T) { reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { - req.SetFormParam("something", "some value") - req.SetQueryParam("hello", "world") - req.SetPathParam("id", "1234") - req.SetHeaderParam("X-Rate-Limit", "200") + _ = req.SetFormParam("something", "some value") + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") return nil }) r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) - r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime) - req, err := r.BuildHTTP(runtime.JSONMime, testProducers, nil) + req, err := r.BuildHTTP(runtime.JSONMime, "", testProducers, nil) if assert.NoError(t, err) && assert.NotNil(t, req) { assert.Equal(t, "200", req.Header.Get("x-rate-limit")) assert.Equal(t, "world", req.URL.Query().Get("hello")) @@ -206,19 +268,134 @@ } } +func TestBuildRequest_BuildHTTP_Form_URLEncoded(t *testing.T) { + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetFormParam("something", "some value") + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.URLencodedFormMime) + + req, err := r.BuildHTTP(runtime.URLencodedFormMime, "", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, runtime.URLencodedFormMime, req.Header.Get(runtime.HeaderContentType)) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/flats/1234/", req.URL.Path) + expected := []byte("something=some+value") + actual, _ := ioutil.ReadAll(req.Body) + assert.Equal(t, expected, actual) + } +} + +func TestBuildRequest_BuildHTTP_Form_Content_Length(t *testing.T) { + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetFormParam("something", "some value") + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.MultipartFormMime) + + req, err := r.BuildHTTP(runtime.JSONMime, "", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/flats/1234/", req.URL.Path) + assert.Condition(t, func() bool { return req.ContentLength > 0 }, + "ContentLength must great than 0. got %d", req.ContentLength) + expected := []byte("something=some+value") + actual, _ := ioutil.ReadAll(req.Body) + assert.Equal(t, expected, actual) + } +} + +func TestBuildRequest_BuildHTTP_FormMultipart(t *testing.T) { + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetFormParam("something", "some value") + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.MultipartFormMime) + + req, err := r.BuildHTTP(runtime.MultipartFormMime, "", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/flats/1234/", req.URL.Path) + expected1 := []byte("Content-Disposition: form-data; name=\"something\"") + expected2 := []byte("some value") + actual, _ := ioutil.ReadAll(req.Body) + actuallines := bytes.Split(actual, []byte("\r\n")) + assert.Equal(t, 6, len(actuallines)) + boundary := string(actuallines[0]) + lastboundary := string(actuallines[4]) + assert.True(t, strings.HasPrefix(boundary, "--")) + assert.True(t, strings.HasPrefix(lastboundary, "--") && strings.HasSuffix(lastboundary, "--")) + assert.Equal(t, lastboundary, boundary+"--") + assert.Equal(t, expected1, actuallines[1]) + assert.Equal(t, expected2, actuallines[3]) + } +} + +func TestBuildRequest_BuildHTTP_FormMultiples(t *testing.T) { + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetFormParam("something", "some value", "another value") + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.MultipartFormMime) + + req, err := r.BuildHTTP(runtime.MultipartFormMime, "", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/flats/1234/", req.URL.Path) + expected1 := []byte("Content-Disposition: form-data; name=\"something\"") + expected2 := []byte("some value") + expected3 := []byte("another value") + actual, _ := ioutil.ReadAll(req.Body) + actuallines := bytes.Split(actual, []byte("\r\n")) + assert.Equal(t, 10, len(actuallines)) + boundary := string(actuallines[0]) + lastboundary := string(actuallines[8]) + assert.True(t, strings.HasPrefix(boundary, "--")) + assert.True(t, strings.HasPrefix(lastboundary, "--") && strings.HasSuffix(lastboundary, "--")) + assert.Equal(t, lastboundary, boundary+"--") + assert.Equal(t, expected1, actuallines[1]) + assert.Equal(t, expected2, actuallines[3]) + assert.Equal(t, actuallines[0], actuallines[4]) + assert.Equal(t, expected1, actuallines[5]) + assert.Equal(t, expected3, actuallines[7]) + } +} + func TestBuildRequest_BuildHTTP_Files(t *testing.T) { cont, _ := ioutil.ReadFile("./runtime.go") + cont2, _ := ioutil.ReadFile("./request.go") reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { - req.SetFormParam("something", "some value") - req.SetFileParam("file", mustGetFile("./runtime.go")) - req.SetQueryParam("hello", "world") - req.SetPathParam("id", "1234") - req.SetHeaderParam("X-Rate-Limit", "200") + _ = req.SetFormParam("something", "some value") + _ = req.SetFileParam("file", mustGetFile("./runtime.go")) + _ = req.SetFileParam("otherfiles", mustGetFile("./runtime.go"), mustGetFile("./request.go")) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") return nil }) r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) - r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime) - req, err := r.BuildHTTP(runtime.JSONMime, testProducers, nil) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime) + req, err := r.BuildHTTP(runtime.JSONMime, "", testProducers, nil) if assert.NoError(t, err) && assert.NotNil(t, req) { assert.Equal(t, "200", req.Header.Get("x-rate-limit")) assert.Equal(t, "world", req.URL.Query().Get("hello")) @@ -232,13 +409,103 @@ frm, err := mr.ReadForm(1 << 20) if assert.NoError(t, err) { assert.Equal(t, "some value", frm.Value["something"][0]) - mpff := frm.File["file"][0] - mpf, _ := mpff.Open() - defer mpf.Close() - assert.Equal(t, "runtime.go", mpff.Filename) - actual, _ := ioutil.ReadAll(mpf) - assert.Equal(t, cont, actual) + fileverifier := func(name string, index int, filename string, content []byte) { + mpff := frm.File[name][index] + mpf, _ := mpff.Open() + defer mpf.Close() + assert.Equal(t, filename, mpff.Filename) + actual, _ := ioutil.ReadAll(mpf) + assert.Equal(t, content, actual) + } + fileverifier("file", 0, "runtime.go", cont) + + fileverifier("otherfiles", 0, "runtime.go", cont) + fileverifier("otherfiles", 1, "request.go", cont2) } } } } +func TestBuildRequest_BuildHTTP_Files_URLEncoded(t *testing.T) { + cont, _ := ioutil.ReadFile("./runtime.go") + cont2, _ := ioutil.ReadFile("./request.go") + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetFormParam("something", "some value") + _ = req.SetFileParam("file", mustGetFile("./runtime.go")) + _ = req.SetFileParam("otherfiles", mustGetFile("./runtime.go"), mustGetFile("./request.go")) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + r, _ := newRequest("GET", "/flats/{id}/", reqWrtr) + _ = r.SetHeaderParam(runtime.HeaderContentType, runtime.URLencodedFormMime) + req, err := r.BuildHTTP(runtime.URLencodedFormMime, "", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/flats/1234/", req.URL.Path) + mediaType, params, err := mime.ParseMediaType(req.Header.Get(runtime.HeaderContentType)) + if assert.NoError(t, err) { + assert.Equal(t, runtime.URLencodedFormMime, mediaType) + boundary := params["boundary"] + mr := multipart.NewReader(req.Body, boundary) + defer req.Body.Close() + frm, err := mr.ReadForm(1 << 20) + if assert.NoError(t, err) { + assert.Equal(t, "some value", frm.Value["something"][0]) + fileverifier := func(name string, index int, filename string, content []byte) { + mpff := frm.File[name][index] + mpf, _ := mpff.Open() + defer mpf.Close() + assert.Equal(t, filename, mpff.Filename) + actual, _ := ioutil.ReadAll(mpf) + assert.Equal(t, content, actual) + } + fileverifier("file", 0, "runtime.go", cont) + + fileverifier("otherfiles", 0, "runtime.go", cont) + fileverifier("otherfiles", 1, "request.go", cont2) + } + } + } +} + +func TestBuildRequest_BuildHTTP_BasePath(t *testing.T) { + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetBodyParam(nil) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + r, _ := newRequest("POST", "/flats/{id}/", reqWrtr) + + req, err := r.BuildHTTP(runtime.JSONMime, "/basepath", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/basepath/flats/1234/", req.URL.Path) + assert.Equal(t, runtime.JSONMime, req.Header.Get(runtime.HeaderContentType)) + } +} + +func TestBuildRequest_BuildHTTP_EscapedPath(t *testing.T) { + reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { + _ = req.SetBodyParam(nil) + _ = req.SetQueryParam("hello", "world") + _ = req.SetPathParam("id", "1234/?*&^%") + _ = req.SetHeaderParam("X-Rate-Limit", "200") + return nil + }) + r, _ := newRequest("POST", "/flats/{id}/", reqWrtr) + + req, err := r.BuildHTTP(runtime.JSONMime, "/basepath", testProducers, nil) + if assert.NoError(t, err) && assert.NotNil(t, req) { + assert.Equal(t, "200", req.Header.Get("x-rate-limit")) + assert.Equal(t, "world", req.URL.Query().Get("hello")) + assert.Equal(t, "/basepath/flats/1234/?*&^%/", req.URL.Path) + assert.Equal(t, "/basepath/flats/1234%2F%3F%2A&%5E%25/", req.URL.RawPath) + assert.Equal(t, req.URL.RawPath, req.URL.EscapedPath()) + assert.Equal(t, runtime.JSONMime, req.Header.Get(runtime.HeaderContentType)) + } +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/runtime.go golang-github-go-openapi-runtime-0.15.0/client/runtime.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/runtime.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client/runtime.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,12 +15,17 @@ package client import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" "fmt" + "io/ioutil" "mime" "net/http" "net/http/httputil" - "os" - "path" "strings" "sync" "time" @@ -29,9 +34,143 @@ "golang.org/x/net/context/ctxhttp" "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/logger" + "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/strfmt" ) +// TLSClientOptions to configure client authentication with mutual TLS +type TLSClientOptions struct { + // Certificate is the path to a PEM-encoded certificate to be used for + // client authentication. If set then Key must also be set. + Certificate string + + // LoadedCertificate is the certificate to be used for client authentication. + // This field is ignored if Certificate is set. If this field is set, LoadedKey + // is also required. + LoadedCertificate *x509.Certificate + + // Key is the path to an unencrypted PEM-encoded private key for client + // authentication. This field is required if Certificate is set. + Key string + + // LoadedKey is the key for client authentication. This field is required if + // LoadedCertificate is set. + LoadedKey crypto.PrivateKey + + // CA is a path to a PEM-encoded certificate that specifies the root certificate + // to use when validating the TLS certificate presented by the server. If this field + // (and LoadedCA) is not set, the system certificate pool is used. This field is ignored if LoadedCA + // is set. + CA string + + // LoadedCA specifies the root certificate to use when validating the server's TLS certificate. + // If this field (and CA) is not set, the system certificate pool is used. + LoadedCA *x509.Certificate + + // ServerName specifies the hostname to use when verifying the server certificate. + // If this field is set then InsecureSkipVerify will be ignored and treated as + // false. + ServerName string + + // InsecureSkipVerify controls whether the certificate chain and hostname presented + // by the server are validated. If false, any certificate is accepted. + InsecureSkipVerify bool + + // Prevents callers using unkeyed fields. + _ struct{} +} + +// TLSClientAuth creates a tls.Config for mutual auth +func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) { + // create client tls config + cfg := &tls.Config{} + + // load client cert if specified + if opts.Certificate != "" { + cert, err := tls.LoadX509KeyPair(opts.Certificate, opts.Key) + if err != nil { + return nil, fmt.Errorf("tls client cert: %v", err) + } + cfg.Certificates = []tls.Certificate{cert} + } else if opts.LoadedCertificate != nil { + block := pem.Block{Type: "CERTIFICATE", Bytes: opts.LoadedCertificate.Raw} + certPem := pem.EncodeToMemory(&block) + + var keyBytes []byte + switch k := opts.LoadedKey.(type) { + case *rsa.PrivateKey: + keyBytes = x509.MarshalPKCS1PrivateKey(k) + case *ecdsa.PrivateKey: + var err error + keyBytes, err = x509.MarshalECPrivateKey(k) + if err != nil { + return nil, fmt.Errorf("tls client priv key: %v", err) + } + default: + return nil, fmt.Errorf("tls client priv key: unsupported key type") + } + + block = pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes} + keyPem := pem.EncodeToMemory(&block) + + cert, err := tls.X509KeyPair(certPem, keyPem) + if err != nil { + return nil, fmt.Errorf("tls client cert: %v", err) + } + cfg.Certificates = []tls.Certificate{cert} + } + + cfg.InsecureSkipVerify = opts.InsecureSkipVerify + + // When no CA certificate is provided, default to the system cert pool + // that way when a request is made to a server known by the system trust store, + // the name is still verified + if opts.LoadedCA != nil { + caCertPool := x509.NewCertPool() + caCertPool.AddCert(opts.LoadedCA) + cfg.RootCAs = caCertPool + } else if opts.CA != "" { + // load ca cert + caCert, err := ioutil.ReadFile(opts.CA) + if err != nil { + return nil, fmt.Errorf("tls client ca: %v", err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + cfg.RootCAs = caCertPool + } + + // apply servername overrride + if opts.ServerName != "" { + cfg.InsecureSkipVerify = false + cfg.ServerName = opts.ServerName + } + + cfg.BuildNameToCertificate() + + return cfg, nil +} + +// TLSTransport creates a http client transport suitable for mutual tls auth +func TLSTransport(opts TLSClientOptions) (http.RoundTripper, error) { + cfg, err := TLSClientAuth(opts) + if err != nil { + return nil, err + } + + return &http.Transport{TLSClientConfig: cfg}, nil +} + +// TLSClient creates a http.Client for mutual auth +func TLSClient(opts TLSClientOptions) (*http.Client, error) { + transport, err := TLSTransport(opts) + if err != nil { + return nil, err + } + return &http.Client{Transport: transport}, nil +} + // DefaultTimeout the default request timeout var DefaultTimeout = 30 * time.Second @@ -49,12 +188,15 @@ Host string BasePath string Formats strfmt.Registry - Debug bool Context context.Context + Debug bool + logger logger.Logger + clientOnce *sync.Once client *http.Client schemes []string + do func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) } // New creates a new default runtime for a swagger api runtime.Client @@ -64,14 +206,18 @@ // TODO: actually infer this stuff from the spec rt.Consumers = map[string]runtime.Consumer{ - runtime.JSONMime: runtime.JSONConsumer(), - runtime.XMLMime: runtime.XMLConsumer(), - runtime.TextMime: runtime.TextConsumer(), + runtime.JSONMime: runtime.JSONConsumer(), + runtime.XMLMime: runtime.XMLConsumer(), + runtime.TextMime: runtime.TextConsumer(), + runtime.HTMLMime: runtime.TextConsumer(), + runtime.DefaultMime: runtime.ByteStreamConsumer(), } rt.Producers = map[string]runtime.Producer{ - runtime.JSONMime: runtime.JSONProducer(), - runtime.XMLMime: runtime.XMLProducer(), - runtime.TextMime: runtime.TextProducer(), + runtime.JSONMime: runtime.JSONProducer(), + runtime.XMLMime: runtime.XMLProducer(), + runtime.TextMime: runtime.TextProducer(), + runtime.HTMLMime: runtime.TextProducer(), + runtime.DefaultMime: runtime.ByteStreamProducer(), } rt.Transport = http.DefaultTransport rt.Jar = nil @@ -82,13 +228,28 @@ if !strings.HasPrefix(rt.BasePath, "/") { rt.BasePath = "/" + rt.BasePath } - rt.Debug = os.Getenv("DEBUG") == "1" + + rt.Debug = logger.DebugEnabled() + rt.logger = logger.StandardLogger{} + if len(schemes) > 0 { rt.schemes = schemes } + rt.do = ctxhttp.Do return &rt } +// NewWithClient allows you to create a new transport with a configured http.Client +func NewWithClient(host, basePath string, schemes []string, client *http.Client) *Runtime { + rt := New(host, basePath, schemes) + if client != nil { + rt.clientOnce.Do(func() { + rt.client = client + }) + } + return rt +} + func (r *Runtime) pickScheme(schemes []string) string { if v := r.selectScheme(r.schemes); v != "" { return v @@ -117,6 +278,34 @@ } return scheme } +func transportOrDefault(left, right http.RoundTripper) http.RoundTripper { + if left == nil { + return right + } + return left +} + +// EnableConnectionReuse drains the remaining body from a response +// so that go will reuse the TCP connections. +// +// This is not enabled by default because there are servers where +// the response never gets closed and that would make the code hang forever. +// So instead it's provided as a http client middleware that can be used to override +// any request. +func (r *Runtime) EnableConnectionReuse() { + if r.client == nil { + r.Transport = KeepAliveTransport( + transportOrDefault(r.Transport, http.DefaultTransport), + ) + return + } + + r.client.Transport = KeepAliveTransport( + transportOrDefault(r.client.Transport, + transportOrDefault(r.Transport, http.DefaultTransport), + ), + ) +} // Submit a request and when there is a body on success it will turn that into the result // all other things are turned into an api error for swagger which retains the status code @@ -129,40 +318,40 @@ } var accept []string - for _, mimeType := range operation.ProducesMediaTypes { - accept = append(accept, mimeType) + accept = append(accept, operation.ProducesMediaTypes...) + if err = request.SetHeaderParam(runtime.HeaderAccept, accept...); err != nil { + return nil, err } - request.SetHeaderParam(runtime.HeaderAccept, accept...) if auth == nil && r.DefaultAuthentication != nil { auth = r.DefaultAuthentication } - if auth != nil { - if err := auth.AuthenticateRequest(request, r.Formats); err != nil { - return nil, err - } - } + //if auth != nil { + // if err := auth.AuthenticateRequest(request, r.Formats); err != nil { + // return nil, err + // } + //} // TODO: pick appropriate media type cmt := r.DefaultMediaType - if len(operation.ConsumesMediaTypes) > 0 { - cmt = operation.ConsumesMediaTypes[0] + for _, mediaType := range operation.ConsumesMediaTypes { + // Pick first non-empty media type + if mediaType != "" { + cmt = mediaType + break + } + } + + if _, ok := r.Producers[cmt]; !ok && cmt != runtime.MultipartFormMime { + return nil, fmt.Errorf("none of producers: %v registered. try %s", r.Producers, cmt) } - req, err := request.BuildHTTP(cmt, r.Producers, r.Formats) + req, err := request.buildHTTP(cmt, r.BasePath, r.Producers, r.Formats, auth) if err != nil { return nil, err } req.URL.Scheme = r.pickScheme(operation.Schemes) req.URL.Host = r.Host - var reinstateSlash bool - if req.URL.Path != "" && req.URL.Path != "/" && req.URL.Path[len(req.URL.Path)-1] == '/' { - reinstateSlash = true - } - req.URL.Path = path.Join(r.BasePath, req.URL.Path) - if reinstateSlash { - req.URL.Path = req.URL.Path + "/" - } r.clientOnce.Do(func() { r.client = &http.Client{ @@ -176,17 +365,36 @@ if err2 != nil { return nil, err2 } - fmt.Println(string(b)) + r.logger.Debugf("%s\n", string(b)) } - pctx := r.Context + var hasTimeout bool + pctx := operation.Context + if pctx == nil { + pctx = r.Context + } else { + hasTimeout = true + } if pctx == nil { pctx = context.Background() } - ctx, cancel := context.WithTimeout(pctx, request.timeout) + var ctx context.Context + var cancel context.CancelFunc + if hasTimeout { + ctx, cancel = context.WithCancel(pctx) + } else { + ctx, cancel = context.WithTimeout(pctx, request.timeout) + } defer cancel() - res, err := ctxhttp.Do(ctx, r.client, req) // make requests, by default follows 10 redirects before failing + client := operation.Client + if client == nil { + client = r.client + } + if r.do == nil { + r.do = ctxhttp.Do + } + res, err := r.do(ctx, client, req) // make requests, by default follows 10 redirects before failing if err != nil { return nil, err } @@ -197,7 +405,7 @@ if err2 != nil { return nil, err2 } - fmt.Println(string(b)) + r.logger.Debugf("%s\n", string(b)) } ct := res.Header.Get(runtime.HeaderContentType) @@ -212,8 +420,24 @@ cons, ok := r.Consumers[mt] if !ok { - // scream about not knowing what to do - return nil, fmt.Errorf("no consumer: %q", ct) + if cons, ok = r.Consumers["*/*"]; !ok { + // scream about not knowing what to do + return nil, fmt.Errorf("no consumer: %q", ct) + } } return readResponse.ReadResponse(response{res}, cons) } + +// SetDebug changes the debug flag. +// It ensures that client and middlewares have the set debug level. +func (r *Runtime) SetDebug(debug bool) { + r.Debug = debug + middleware.Debug = debug +} + +// SetLogger changes the logger stream. +// It ensures that client and middlewares use the same logger. +func (r *Runtime) SetLogger(logger logger.Logger) { + r.logger = logger + middleware.Logger = logger +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/runtime_test.go golang-github-go-openapi-runtime-0.15.0/client/runtime_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client/runtime_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client/runtime_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -24,12 +24,19 @@ "net/http/cookiejar" "net/http/httptest" "net/url" + "os" "testing" "time" + "golang.org/x/net/context" + + "crypto/x509" + "encoding/pem" + "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // task This describes a task. Tasks require a content property to be set. @@ -45,6 +52,108 @@ ID int64 `json:"id" xml:"id"` } +func TestRuntime_TLSAuthConfig(t *testing.T) { + var opts TLSClientOptions + opts.CA = "../fixtures/certs/myCA.crt" + opts.Key = "../fixtures/certs/myclient.key" + opts.Certificate = "../fixtures/certs/myclient.crt" + opts.ServerName = "somewhere" + + cfg, err := TLSClientAuth(opts) + if assert.NoError(t, err) { + if assert.NotNil(t, cfg) { + assert.Len(t, cfg.Certificates, 1) + assert.NotNil(t, cfg.RootCAs) + assert.Equal(t, "somewhere", cfg.ServerName) + } + } +} + +func TestRuntime_TLSAuthConfigWithRSAKey(t *testing.T) { + + keyPem, err := ioutil.ReadFile("../fixtures/certs/myclient.key") + require.NoError(t, err) + + keyDer, _ := pem.Decode(keyPem) + require.NotNil(t, keyDer) + + key, err := x509.ParsePKCS1PrivateKey(keyDer.Bytes) + require.NoError(t, err) + + certPem, err := ioutil.ReadFile("../fixtures/certs/myclient.crt") + require.NoError(t, err) + + certDer, _ := pem.Decode(certPem) + require.NotNil(t, certDer) + + cert, err := x509.ParseCertificate(certDer.Bytes) + + var opts TLSClientOptions + opts.LoadedKey = key + opts.LoadedCertificate = cert + + cfg, err := TLSClientAuth(opts) + if assert.NoError(t, err) { + if assert.NotNil(t, cfg) { + assert.Len(t, cfg.Certificates, 1) + } + } +} + +func TestRuntime_TLSAuthConfigWithECKey(t *testing.T) { + + keyPem, err := ioutil.ReadFile("../fixtures/certs/myclient-ecc.key") + require.NoError(t, err) + + _, remainder := pem.Decode(keyPem) + keyDer, _ := pem.Decode(remainder) + require.NotNil(t, keyDer) + + key, err := x509.ParseECPrivateKey(keyDer.Bytes) + require.NoError(t, err) + + certPem, err := ioutil.ReadFile("../fixtures/certs/myclient-ecc.crt") + require.NoError(t, err) + + certDer, _ := pem.Decode(certPem) + require.NotNil(t, certDer) + + cert, err := x509.ParseCertificate(certDer.Bytes) + + var opts TLSClientOptions + opts.LoadedKey = key + opts.LoadedCertificate = cert + + cfg, err := TLSClientAuth(opts) + if assert.NoError(t, err) { + if assert.NotNil(t, cfg) { + assert.Len(t, cfg.Certificates, 1) + } + } +} + +func TestRuntime_TLSAuthConfigWithLoadedCA(t *testing.T) { + + certPem, err := ioutil.ReadFile("../fixtures/certs/myCA.crt") + require.NoError(t, err) + + block, _ := pem.Decode(certPem) + require.NotNil(t, block) + + cert, err := x509.ParseCertificate(block.Bytes) + require.NoError(t, err) + + var opts TLSClientOptions + opts.LoadedCA = cert + + cfg, err := TLSClientAuth(opts) + if assert.NoError(t, err) { + if assert.NotNil(t, cfg) { + assert.NotNil(t, cfg.RootCAs) + } + } +} + func TestRuntime_Concurrent(t *testing.T) { // test that it can make a simple request // and get the response for it. @@ -57,7 +166,7 @@ rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime) rw.WriteHeader(http.StatusOK) jsongen := json.NewEncoder(rw) - jsongen.Encode(result) + _ = jsongen.Encode(result) })) defer server.Close() @@ -133,7 +242,7 @@ rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime) rw.WriteHeader(http.StatusOK) jsongen := json.NewEncoder(rw) - jsongen.Encode(result) + _ = jsongen.Encode(result) })) defer server.Close() @@ -184,7 +293,7 @@ rw.Header().Add(runtime.HeaderContentType, runtime.XMLMime) rw.WriteHeader(http.StatusOK) xmlgen := xml.NewEncoder(rw) - xmlgen.Encode(result) + _ = xmlgen.Encode(result) })) defer server.Close() @@ -225,7 +334,7 @@ server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Add(runtime.HeaderContentType, runtime.TextMime) rw.WriteHeader(http.StatusOK) - rw.Write([]byte(result)) + _, _ = rw.Write([]byte(result)) })) defer server.Close() @@ -285,7 +394,7 @@ resp.Header.Set("content-type", "application/json") buf := bytes.NewBuffer(nil) enc := json.NewEncoder(buf) - enc.Encode(result) + _ = enc.Encode(result) resp.Body = ioutil.NopCloser(buf) return &resp, nil }) @@ -334,7 +443,7 @@ rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime) rw.WriteHeader(http.StatusOK) jsongen := json.NewEncoder(rw) - jsongen.Encode([]task{}) + _ = jsongen.Encode([]task{}) } else { rw.WriteHeader(http.StatusUnauthorized) } @@ -387,7 +496,7 @@ rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime) rw.WriteHeader(http.StatusOK) jsongen := json.NewEncoder(rw) - jsongen.Encode(result) + _ = jsongen.Encode(result) })) defer server.Close() @@ -435,13 +544,12 @@ rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+";charset=utf-8") rw.WriteHeader(http.StatusOK) jsongen := json.NewEncoder(rw) - jsongen.Encode(result) + _ = jsongen.Encode(result) })) defer server.Close() rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error { - req.SetBodyParam(bytes.NewBufferString("hello")) - return nil + return req.SetBodyParam(bytes.NewBufferString("hello")) }) hu, _ := url.Parse(server.URL) @@ -489,7 +597,7 @@ rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+";charset=utf-8") rw.WriteHeader(http.StatusOK) jsongen := json.NewEncoder(rw) - jsongen.Encode(result) + _ = jsongen.Encode(result) })) defer server.Close() @@ -499,6 +607,7 @@ hu, _ := url.Parse(server.URL) rt := New(hu.Host, "/", []string{"http"}) + rt.do = nil res, err := rt.Submit(&runtime.ClientOperation{ ID: "getTasks", Method: "GET", @@ -542,7 +651,7 @@ rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+";charset=utf-8") rw.WriteHeader(http.StatusOK) jsongen := json.NewEncoder(rw) - jsongen.Encode(result) + _ = jsongen.Encode(result) })) defer server.Close() @@ -580,12 +689,83 @@ } } +func TestRuntime_DebugValue(t *testing.T) { + original := os.Getenv("DEBUG") + + // Emtpy DEBUG means Debug is False + _ = os.Setenv("DEBUG", "") + runtime := New("", "/", []string{"https"}) + assert.False(t, runtime.Debug) + + // Non-Empty Debug means Debug is True + + _ = os.Setenv("DEBUG", "1") + runtime = New("", "/", []string{"https"}) + assert.True(t, runtime.Debug) + + _ = os.Setenv("DEBUG", "true") + runtime = New("", "/", []string{"https"}) + assert.True(t, runtime.Debug) + + _ = os.Setenv("DEBUG", "foo") + runtime = New("", "/", []string{"https"}) + assert.True(t, runtime.Debug) + + // Make sure DEBUG is initial value once again + _ = os.Setenv("DEBUG", original) +} + func TestRuntime_OverrideScheme(t *testing.T) { runtime := New("", "/", []string{"https"}) sch := runtime.pickScheme([]string{"http"}) assert.Equal(t, "https", sch) } +func TestRuntime_OverrideClient(t *testing.T) { + client := &http.Client{} + runtime := NewWithClient("", "/", []string{"https"}, client) + var i int + runtime.clientOnce.Do(func() { i++ }) + assert.Equal(t, client, runtime.client) + assert.Equal(t, 0, i) +} + +func TestRuntime_OverrideClientOperation(t *testing.T) { + client := &http.Client{} + rt := NewWithClient("", "/", []string{"https"}, client) + var i int + rt.clientOnce.Do(func() { i++ }) + assert.Equal(t, client, rt.client) + assert.Equal(t, 0, i) + + var seen *http.Client + rt.do = func(_ context.Context, cl *http.Client, _ *http.Request) (*http.Response, error) { + seen = cl + res := new(http.Response) + res.StatusCode = 200 + res.Body = ioutil.NopCloser(bytes.NewBufferString("OK")) + return res, nil + } + + client2 := new(http.Client) + client2.Timeout = 3 * time.Second + if assert.NotEqual(t, client, client2) { + _, err := rt.Submit(&runtime.ClientOperation{ + Client: client2, + Params: runtime.ClientRequestWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { + return nil + }), + Reader: runtime.ClientResponseReaderFunc(func(_ runtime.ClientResponse, _ runtime.Consumer) (interface{}, error) { + return nil, nil + }), + }) + if assert.NoError(t, err) { + + assert.Equal(t, client2, seen) + } + } +} + func TestRuntime_PreserveTrailingSlash(t *testing.T) { var redirected bool @@ -628,3 +808,71 @@ assert.NoError(t, err) } + +func TestRuntime_FallbackConsumer(t *testing.T) { + result := `W3siY29tcGxldGVkIjpmYWxzZSwiY29udGVudCI6ImRHRnpheUF4SUdOdmJuUmxiblE9IiwiaWQiOjF9XQ==` + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Add(runtime.HeaderContentType, "application/x-task") + rw.WriteHeader(http.StatusOK) + _, _ = rw.Write([]byte(result)) + })) + defer server.Close() + + rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error { + return req.SetBodyParam(bytes.NewBufferString("hello")) + }) + + hu, _ := url.Parse(server.URL) + rt := New(hu.Host, "/", []string{"http"}) + + // without the fallback consumer + _, err := rt.Submit(&runtime.ClientOperation{ + ID: "getTasks", + Method: "POST", + PathPattern: "/", + Schemes: []string{"http"}, + ConsumesMediaTypes: []string{"application/octet-stream"}, + Params: rwrtr, + Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + if response.Code() == 200 { + var result []byte + if err := consumer.Consume(response.Body(), &result); err != nil { + return nil, err + } + return result, nil + } + return nil, errors.New("Generic error") + }), + }) + + if assert.Error(t, err) { + assert.Equal(t, `no consumer: "application/x-task"`, err.Error()) + } + + // add the fallback consumer + rt.Consumers["*/*"] = rt.Consumers[runtime.DefaultMime] + res, err := rt.Submit(&runtime.ClientOperation{ + ID: "getTasks", + Method: "POST", + PathPattern: "/", + Schemes: []string{"http"}, + ConsumesMediaTypes: []string{"application/octet-stream"}, + Params: rwrtr, + Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + if response.Code() == 200 { + var result []byte + if err := consumer.Consume(response.Body(), &result); err != nil { + return nil, err + } + return result, nil + } + return nil, errors.New("Generic error") + }), + }) + + if assert.NoError(t, err) { + assert.IsType(t, []byte{}, res) + actual := res.([]byte) + assert.EqualValues(t, result, actual) + } +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client_operation.go golang-github-go-openapi-runtime-0.15.0/client_operation.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client_operation.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client_operation.go 2018-06-28 22:01:56.000000000 +0000 @@ -14,6 +14,12 @@ package runtime +import ( + "net/http" + + "golang.org/x/net/context" +) + // ClientOperation represents the context for a swagger operation to be submitted to the transport type ClientOperation struct { ID string @@ -25,6 +31,8 @@ AuthInfo ClientAuthInfoWriter Params ClientRequestWriter Reader ClientResponseReader + Context context.Context + Client *http.Client } // A ClientTransport implementor knows how to submit Request objects to some destination diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client_request.go golang-github-go-openapi-runtime-0.15.0/client_request.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client_request.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client_request.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,7 +15,9 @@ package runtime import ( - "os" + "io" + "io/ioutil" + "net/url" "time" "github.com/go-openapi/strfmt" @@ -45,9 +47,50 @@ SetPathParam(string, string) error - SetFileParam(string, *os.File) error + GetQueryParams() url.Values + + SetFileParam(string, ...NamedReadCloser) error SetBodyParam(interface{}) error SetTimeout(time.Duration) error + + GetMethod() string + + GetPath() string + + GetBody() []byte +} + +// NamedReadCloser represents a named ReadCloser interface +type NamedReadCloser interface { + io.ReadCloser + Name() string +} + +// NamedReader creates a NamedReadCloser for use as file upload +func NamedReader(name string, rdr io.Reader) NamedReadCloser { + rc, ok := rdr.(io.ReadCloser) + if !ok { + rc = ioutil.NopCloser(rdr) + } + return &namedReadCloser{ + name: name, + cr: rc, + } +} + +type namedReadCloser struct { + name string + cr io.ReadCloser +} + +func (n *namedReadCloser) Close() error { + return n.cr.Close() +} +func (n *namedReadCloser) Read(p []byte) (int, error) { + return n.cr.Read(p) +} +func (n *namedReadCloser) Name() string { + return n.name } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client_request_test.go golang-github-go-openapi-runtime-0.15.0/client_request_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client_request_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client_request_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -16,7 +16,7 @@ import ( "net/http" - "os" + "net/url" "testing" "time" @@ -43,7 +43,7 @@ func (t *trw) SetPathParam(_ string, _ string) error { return nil } -func (t *trw) SetFileParam(_ string, _ *os.File) error { return nil } +func (t *trw) SetFileParam(_ string, _ ...NamedReadCloser) error { return nil } func (t *trw) SetBodyParam(body interface{}) error { t.Body = body @@ -54,16 +54,24 @@ return nil } +func (t *trw) GetQueryParams() url.Values { return nil } + +func (t *trw) GetMethod() string { return "" } + +func (t *trw) GetPath() string { return "" } + +func (t *trw) GetBody() []byte { return nil } + func TestRequestWriterFunc(t *testing.T) { hand := ClientRequestWriterFunc(func(r ClientRequest, reg strfmt.Registry) error { - r.SetHeaderParam("blah", "blah blah") - r.SetBodyParam(struct{ Name string }{"Adriana"}) + _ = r.SetHeaderParam("blah", "blah blah") + _ = r.SetBodyParam(struct{ Name string }{"Adriana"}) return nil }) tr := new(trw) - hand.WriteToRequest(tr, nil) + _ = hand.WriteToRequest(tr, nil) assert.Equal(t, "blah blah", tr.Headers.Get("blah")) assert.Equal(t, "Adriana", tr.Body.(struct{ Name string }).Name) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client_response_test.go golang-github-go-openapi-runtime-0.15.0/client_response_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/client_response_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/client_response_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -52,7 +52,7 @@ actual.Header = r.GetHeader("blah") return actual, nil }) - reader.ReadResponse(response{}, nil) + _, _ = reader.ReadResponse(response{}, nil) assert.Equal(t, "the content", actual.Body) assert.Equal(t, "the message", actual.Message) assert.Equal(t, "the header", actual.Header) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/constants.go golang-github-go-openapi-runtime-0.15.0/constants.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/constants.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/constants.go 2018-06-28 22:01:56.000000000 +0000 @@ -36,6 +36,10 @@ XMLMime = "application/xml" // TextMime the text mime type TextMime = "text/plain" + // HTMLMime the html mime type + HTMLMime = "text/html" // MultipartFormMime the multipart form mime type MultipartFormMime = "multipart/form-data" + // URLencodedFormMime the url encoded form mime type + URLencodedFormMime = "application/x-www-form-urlencoded" ) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/changelog golang-github-go-openapi-runtime-0.15.0/debian/changelog --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/changelog 2017-03-23 05:17:43.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/changelog 2019-10-29 08:50:11.000000000 +0000 @@ -1,3 +1,23 @@ +golang-github-go-openapi-runtime (0.15.0-1) unstable; urgency=medium + + * Team upload. + + [ Alexandre Viau ] + * Point Vcs-* urls to salsa.debian.org. + + [ Jelmer Vernooij ] + * Use secure copyright file specification URI. + + [ Martina Ferrari ] + * New upstream release. + * Automatic cme fixes. + * Update gbp.conf. + * Stop vendoring the validate library. + * Adjust dependencies. + * Add versioned dependencies. + + -- Martina Ferrari Tue, 29 Oct 2019 08:50:11 +0000 + golang-github-go-openapi-runtime (0.0~git20160704.0.11e322e-1) unstable; urgency=medium * Initial release (Closes: #857100) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/compat golang-github-go-openapi-runtime-0.15.0/debian/compat --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/compat 2017-02-21 22:05:57.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -10 diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/control golang-github-go-openapi-runtime-0.15.0/debian/control --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/control 2017-03-23 04:53:44.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/control 2019-10-29 08:50:11.000000000 +0000 @@ -1,45 +1,48 @@ Source: golang-github-go-openapi-runtime -Section: devel -Priority: extra Maintainer: Debian Go Packaging Team -Uploaders: Tim Potter -Build-Depends: debhelper (>= 10), +Uploaders: Tim Potter , +Section: devel +Testsuite: autopkgtest-pkg-go +Priority: optional +Build-Depends: debhelper-compat (= 12), dh-golang, golang-any, - golang-github-go-openapi-analysis-dev, + golang-github-docker-go-units-dev, + golang-github-go-openapi-analysis-dev (>= 0.15.0~), golang-github-go-openapi-errors-dev, - golang-github-go-openapi-loads-dev, - golang-github-go-openapi-spec-dev, + golang-github-go-openapi-loads-dev (>= 0.15.0~), + golang-github-go-openapi-spec-dev (>= 0.15.0~), golang-github-go-openapi-strfmt-dev, - golang-github-go-openapi-swag-dev, -# golang-github-go-openapi-validate-dev, + golang-github-go-openapi-swag-dev (>= 0.15.0~), + golang-github-go-openapi-validate-dev, golang-github-gorilla-context-dev, golang-github-stretchr-testify-dev, golang-golang-x-net-dev, - golang-gopkg-yaml.v2-dev -Standards-Version: 3.9.8 + golang-gopkg-yaml.v2-dev, +Standards-Version: 4.4.1 +Vcs-Browser: https://salsa.debian.org/go-team/packages/golang-github-go-openapi-runtime +Vcs-Git: https://salsa.debian.org/go-team/packages/golang-github-go-openapi-runtime.git Homepage: https://github.com/go-openapi/runtime -Vcs-Browser: https://anonscm.debian.org/cgit/pkg-go/packages/golang-github-go-openapi-runtime.git -Vcs-Git: https://anonscm.debian.org/git/pkg-go/packages/golang-github-go-openapi-runtime.git XS-Go-Import-Path: github.com/go-openapi/runtime Package: golang-github-go-openapi-runtime-dev Architecture: all -Depends: ${shlibs:Depends}, - ${misc:Depends}, - golang-github-go-openapi-analysis-dev, +Depends: golang-github-docker-go-units-dev, + golang-github-go-openapi-analysis-dev (>= 0.15.0~), golang-github-go-openapi-errors-dev, - golang-github-go-openapi-loads-dev, - golang-github-go-openapi-spec-dev, + golang-github-go-openapi-loads-dev (>= 0.15.0~), + golang-github-go-openapi-spec-dev (>= 0.15.0~), golang-github-go-openapi-strfmt-dev, - golang-github-go-openapi-swag-dev, -# golang-github-go-openapi-validate-dev, + golang-github-go-openapi-swag-dev (>= 0.15.0~), + golang-github-go-openapi-validate-dev, golang-github-gorilla-context-dev, golang-github-stretchr-testify-dev, golang-golang-x-net-dev, - golang-gopkg-yaml.v2-dev + golang-gopkg-yaml.v2-dev, + ${misc:Depends}, + ${shlibs:Depends}, Description: OpenAPI runtime interfaces - The validate library is part of the OpenAPI Initiative and OpenAPI + The runtime library is part of the OpenAPI Initiative and OpenAPI Specification, and consists of classes and functions to be used in code generation when creating client- and server-side implementations of REST interfaces. diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/copyright golang-github-go-openapi-runtime-0.15.0/debian/copyright --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/copyright 2017-03-23 05:17:43.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/copyright 2019-10-29 08:50:11.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: runtime Source: https://github.com/go-openapi/runtime @@ -14,10 +14,6 @@ Copyright: 2014 Naoya Inada License: Expat -Files: vendor/github.com/go-openapi/validate -Copyright: 2015 go-swagger maintainers -License: Apache-2.0 - Files: debian/* Copyright: 2017 Tim Potter License: Apache-2.0 diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/gbp.conf golang-github-go-openapi-runtime-0.15.0/debian/gbp.conf --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/gbp.conf 2017-02-21 22:05:59.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/gbp.conf 2019-10-29 08:50:11.000000000 +0000 @@ -1,2 +1,6 @@ [DEFAULT] -pristine-tar = True +debian-branch = debian/sid + +[buildpackage] +dist = DEP14 +upstream-tag = upstream/%(version)s diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/gitlab-ci.yml golang-github-go-openapi-runtime-0.15.0/debian/gitlab-ci.yml --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/gitlab-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/gitlab-ci.yml 2019-10-29 08:50:11.000000000 +0000 @@ -0,0 +1,28 @@ + +# auto-generated, DO NOT MODIFY. +# The authoritative copy of this file lives at: +# https://salsa.debian.org/go-team/ci/blob/master/cmd/ci/gitlabciyml.go + +# TODO: publish under debian-go-team/ci +image: stapelberg/ci2 + +test_the_archive: + artifacts: + paths: + - before-applying-commit.json + - after-applying-commit.json + script: + # Create an overlay to discard writes to /srv/gopath/src after the build: + - "rm -rf /cache/overlay/{upper,work}" + - "mkdir -p /cache/overlay/{upper,work}" + - "mount -t overlay overlay -o lowerdir=/srv/gopath/src,upperdir=/cache/overlay/upper,workdir=/cache/overlay/work /srv/gopath/src" + - "export GOPATH=/srv/gopath" + - "export GOCACHE=/cache/go" + # Build the world as-is: + - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > before-applying-commit.json" + # Copy this package into the overlay: + - "GBP_CONF_FILES=:debian/gbp.conf gbp buildpackage --git-no-pristine-tar --git-ignore-branch --git-ignore-new --git-export-dir=/tmp/export --git-no-overlay --git-tarball-dir=/nonexistant --git-cleaner=/bin/true --git-builder='dpkg-buildpackage -S -d --no-sign'" + - "pgt-gopath -dsc /tmp/export/*.dsc" + # Rebuild the world: + - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > after-applying-commit.json" + - "ci-diff before-applying-commit.json after-applying-commit.json" diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/patches/series golang-github-go-openapi-runtime-0.15.0/debian/patches/series --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/patches/series 2017-03-23 04:50:01.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/patches/series 2019-10-29 08:50:11.000000000 +0000 @@ -1,2 +1 @@ -vendor-go-openapi-validate.patch test-fixture-locations.patch diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/patches/test-fixture-locations.patch golang-github-go-openapi-runtime-0.15.0/debian/patches/test-fixture-locations.patch --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/patches/test-fixture-locations.patch 2017-03-23 04:50:30.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/patches/test-fixture-locations.patch 2019-10-29 08:50:11.000000000 +0000 @@ -1,3 +1,4 @@ +Description: Test fixture locations Index: golang-github-go-openapi-runtime/middleware/context_test.go =================================================================== --- golang-github-go-openapi-runtime.orig/middleware/context_test.go diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/patches/vendor-go-openapi-validate.patch golang-github-go-openapi-runtime-0.15.0/debian/patches/vendor-go-openapi-validate.patch --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/debian/patches/vendor-go-openapi-validate.patch 2017-03-23 04:44:15.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/debian/patches/vendor-go-openapi-validate.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,3044 +0,0 @@ -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.drone.sec -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.drone.sec -@@ -0,0 +1 @@ -+eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.lisG21ATunZSCBP6vaqK_AZCIR18tN563RdqkAb6PGOipqTeg8R7VwZQIeDqS-2Vond6NX_KSC_D_uxxv0hBf2DiGXmwMUmP4nRXrsmbzT2qQKKIHYRDC_6jb2-FSfK14ezIe1Q07UiiJecDsN3CFEccS8E68Tdnp78p7yDwbTvpumnZmwYfyhlImtjFQv2YpyFVsjEHWK0R4e9T3ONQWcx6D2rSoxABbutrS03QwsJhHCeD9joL_gxfkFKm3CW8yWPSk2QYtx_Q1hu-tZR4IPb2tQPXPX3mtyhwBqziWgmJRDFCEjlCO5aCobiMm_9K5X05gue_DcgW163zh1P9jg.nleER2An8CUn_OuR.b77RFEFp0gC8j5yCAoARNKYmQIvWq99ibmf5ffJgdhQBF3sRYJLt_XflJ_2lsaiFOxvc45T2fnkMVy2lHFcri7F9f1BRiT_0AcDthxsecGzG8BZ9QvaM6b4Dn0rhjrOq8rsF0m3ZnbPBkkg3LV5EkbHWstMo2fgJPJhJswlGWhqJPJBDecG1nMBC8SMH32X-zVlSM-BLiaghvOGNxyb_RLZJZ3CLczIdQ2JO2UeYkOGCPGzernvkHDMpqQXc-8cmulDdHgCy87qFLy5ttGFgYbnTm92h_ChOGKZixeX0PL0pQY5wXd2xTO7Tg_Ov5E5FoVwIkwOextedVsF9iz_b_mwtCY3LXrvbJTW7zWrwBVsVyAXxT5iu0HyQ3tBVxT2GxS-yM5ApqLozcZCQg9flMyfSgThu82FfzEr0fI5vKw8zo0GdO4GBuVSppM9m6ToG6hlwyHD9g2YTZw9068hyq1_kZQhugJRjgGbpa2gyGqzx16fg0zVoupVIiq5KfvRlAQFeOVVjQwb0BWf25tJUj5tV3O9ge6dbKSXizEca33FJJwJWoXhd7DCREXUU9pBz06NCCf495BGoVbq3oLPDQc2mpcuy0XAPxSwXcc5Ts8DNs7MrxBlYdw81wMXuztIpOY4.XjKlMWl_H40XszToi2VU5g -\ No newline at end of file -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.drone.yml -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.drone.yml -@@ -0,0 +1,40 @@ -+clone: -+ path: github.com/go-openapi/validate -+ -+matrix: -+ GO_VERSION: -+ - "1.6" -+ -+build: -+ integration: -+ image: golang:$$GO_VERSION -+ pull: true -+ commands: -+ - go get -u github.com/axw/gocov/gocov -+ - go get -u gopkg.in/matm/v1/gocov-html -+ - go get -u github.com/cee-dub/go-junit-report -+ - go get -u github.com/stretchr/testify/assert -+ - go get -u gopkg.in/yaml.v2 -+ - go get -u github.com/go-openapi/analysis -+ - go get -u github.com/go-openapi/errors -+ - go get -u github.com/go-openapi/loads -+ - go get -u github.com/go-openapi/strfmt -+ - go get -u github.com/go-openapi/runtime -+ - go test -race -+ - go test -v -cover -coverprofile=coverage.out -covermode=count -+ -+notify: -+ slack: -+ channel: bots -+ webhook_url: $$SLACK_URL -+ username: drone -+ -+publish: -+ coverage: -+ server: https://coverage.vmware.run -+ token: $$GITHUB_TOKEN -+ # threshold: 70 -+ # must_increase: true -+ when: -+ matrix: -+ GO_VERSION: "1.6" -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.github/CONTRIBUTING.md -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.github/CONTRIBUTING.md -@@ -0,0 +1,117 @@ -+## Contribution Guidelines -+ -+### Pull requests are always welcome -+ -+We are always thrilled to receive pull requests, and do our best to -+process them as fast as possible. Not sure if that typo is worth a pull -+request? Do it! We will appreciate it. -+ -+If your pull request is not accepted on the first try, don't be -+discouraged! If there's a problem with the implementation, hopefully you -+received feedback on what to improve. -+ -+We're trying very hard to keep go-swagger lean and focused. We don't want it -+to do everything for everybody. This means that we might decide against -+incorporating a new feature. However, there might be a way to implement -+that feature *on top of* go-swagger. -+ -+ -+### Conventions -+ -+Fork the repo and make changes on your fork in a feature branch: -+ -+- If it's a bugfix branch, name it XXX-something where XXX is the number of the -+ issue -+- If it's a feature branch, create an enhancement issue to announce your -+ intentions, and name it XXX-something where XXX is the number of the issue. -+ -+Submit unit tests for your changes. Go has a great test framework built in; use -+it! Take a look at existing tests for inspiration. Run the full test suite on -+your branch before submitting a pull request. -+ -+Update the documentation when creating or modifying features. Test -+your documentation changes for clarity, concision, and correctness, as -+well as a clean documentation build. See ``docs/README.md`` for more -+information on building the docs and how docs get released. -+ -+Write clean code. Universally formatted code promotes ease of writing, reading, -+and maintenance. Always run `gofmt -s -w file.go` on each changed file before -+committing your changes. Most editors have plugins that do this automatically. -+ -+Pull requests descriptions should be as clear as possible and include a -+reference to all the issues that they address. -+ -+Pull requests must not contain commits from other users or branches. -+ -+Commit messages must start with a capitalized and short summary (max. 50 -+chars) written in the imperative, followed by an optional, more detailed -+explanatory text which is separated from the summary by an empty line. -+ -+Code review comments may be added to your pull request. Discuss, then make the -+suggested modifications and push additional commits to your feature branch. Be -+sure to post a comment after pushing. The new commits will show up in the pull -+request automatically, but the reviewers will not be notified unless you -+comment. -+ -+Before the pull request is merged, make sure that you squash your commits into -+logical units of work using `git rebase -i` and `git push -f`. After every -+commit the test suite should be passing. Include documentation changes in the -+same commit so that a revert would remove all traces of the feature or fix. -+ -+Commits that fix or close an issue should include a reference like `Closes #XXX` -+or `Fixes #XXX`, which will automatically close the issue when merged. -+ -+### Sign your work -+ -+The sign-off is a simple line at the end of the explanation for the -+patch, which certifies that you wrote it or otherwise have the right to -+pass it on as an open-source patch. The rules are pretty simple: if you -+can certify the below (from -+[developercertificate.org](http://developercertificate.org/)): -+ -+``` -+Developer Certificate of Origin -+Version 1.1 -+ -+Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -+660 York Street, Suite 102, -+San Francisco, CA 94110 USA -+ -+Everyone is permitted to copy and distribute verbatim copies of this -+license document, but changing it is not allowed. -+ -+ -+Developer's Certificate of Origin 1.1 -+ -+By making a contribution to this project, I certify that: -+ -+(a) The contribution was created in whole or in part by me and I -+ have the right to submit it under the open source license -+ indicated in the file; or -+ -+(b) The contribution is based upon previous work that, to the best -+ of my knowledge, is covered under an appropriate open source -+ license and I have the right under that license to submit that -+ work with modifications, whether created in whole or in part -+ by me, under the same open source license (unless I am -+ permitted to submit under a different license), as indicated -+ in the file; or -+ -+(c) The contribution was provided directly to me by some other -+ person who certified (a), (b) or (c) and I have not modified -+ it. -+ -+(d) I understand and agree that this project and the contribution -+ are public and that a record of the contribution (including all -+ personal information I submit with it, including my sign-off) is -+ maintained indefinitely and may be redistributed consistent with -+ this project or the open source license(s) involved. -+``` -+ -+then you just add a line to every git commit message: -+ -+ Signed-off-by: Joe Smith -+ -+using your real name (sorry, no pseudonyms or anonymous contributions.) -+ -+You can add the sign off when creating the git commit via `git commit -s`. -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.gitignore -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.gitignore -@@ -0,0 +1 @@ -+.pc -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.pullapprove.yml -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/.pullapprove.yml -@@ -0,0 +1,13 @@ -+approve_by_comment: true -+approve_regex: '^(:shipit:|:\+1:|\+1|LGTM|lgtm|Approved)' -+reject_regex: ^[Rr]ejected -+reset_on_push: false -+reviewers: -+ members: -+ - casualjim -+ - chancez -+ - frapposelli -+ - vburenin -+ - pytlesk4 -+ name: pullapprove -+ required: 1 -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/CODE_OF_CONDUCT.md -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/CODE_OF_CONDUCT.md -@@ -0,0 +1,74 @@ -+# Contributor Covenant Code of Conduct -+ -+## Our Pledge -+ -+In the interest of fostering an open and welcoming environment, we as -+contributors and maintainers pledge to making participation in our project and -+our community a harassment-free experience for everyone, regardless of age, body -+size, disability, ethnicity, gender identity and expression, level of experience, -+nationality, personal appearance, race, religion, or sexual identity and -+orientation. -+ -+## Our Standards -+ -+Examples of behavior that contributes to creating a positive environment -+include: -+ -+* Using welcoming and inclusive language -+* Being respectful of differing viewpoints and experiences -+* Gracefully accepting constructive criticism -+* Focusing on what is best for the community -+* Showing empathy towards other community members -+ -+Examples of unacceptable behavior by participants include: -+ -+* The use of sexualized language or imagery and unwelcome sexual attention or -+advances -+* Trolling, insulting/derogatory comments, and personal or political attacks -+* Public or private harassment -+* Publishing others' private information, such as a physical or electronic -+ address, without explicit permission -+* Other conduct which could reasonably be considered inappropriate in a -+ professional setting -+ -+## Our Responsibilities -+ -+Project maintainers are responsible for clarifying the standards of acceptable -+behavior and are expected to take appropriate and fair corrective action in -+response to any instances of unacceptable behavior. -+ -+Project maintainers have the right and responsibility to remove, edit, or -+reject comments, commits, code, wiki edits, issues, and other contributions -+that are not aligned to this Code of Conduct, or to ban temporarily or -+permanently any contributor for other behaviors that they deem inappropriate, -+threatening, offensive, or harmful. -+ -+## Scope -+ -+This Code of Conduct applies both within project spaces and in public spaces -+when an individual is representing the project or its community. Examples of -+representing a project or community include using an official project e-mail -+address, posting via an official social media account, or acting as an appointed -+representative at an online or offline event. Representation of a project may be -+further defined and clarified by project maintainers. -+ -+## Enforcement -+ -+Instances of abusive, harassing, or otherwise unacceptable behavior may be -+reported by contacting the project team at ivan+abuse@flanders.co.nz. All -+complaints will be reviewed and investigated and will result in a response that -+is deemed necessary and appropriate to the circumstances. The project team is -+obligated to maintain confidentiality with regard to the reporter of an incident. -+Further details of specific enforcement policies may be posted separately. -+ -+Project maintainers who do not follow or enforce the Code of Conduct in good -+faith may face temporary or permanent repercussions as determined by other -+members of the project's leadership. -+ -+## Attribution -+ -+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -+available at [http://contributor-covenant.org/version/1/4][version] -+ -+[homepage]: http://contributor-covenant.org -+[version]: http://contributor-covenant.org/version/1/4/ -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/LICENSE -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/LICENSE -@@ -0,0 +1,202 @@ -+ -+ Apache License -+ Version 2.0, January 2004 -+ http://www.apache.org/licenses/ -+ -+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -+ -+ 1. Definitions. -+ -+ "License" shall mean the terms and conditions for use, reproduction, -+ and distribution as defined by Sections 1 through 9 of this document. -+ -+ "Licensor" shall mean the copyright owner or entity authorized by -+ the copyright owner that is granting the License. -+ -+ "Legal Entity" shall mean the union of the acting entity and all -+ other entities that control, are controlled by, or are under common -+ control with that entity. For the purposes of this definition, -+ "control" means (i) the power, direct or indirect, to cause the -+ direction or management of such entity, whether by contract or -+ otherwise, or (ii) ownership of fifty percent (50%) or more of the -+ outstanding shares, or (iii) beneficial ownership of such entity. -+ -+ "You" (or "Your") shall mean an individual or Legal Entity -+ exercising permissions granted by this License. -+ -+ "Source" form shall mean the preferred form for making modifications, -+ including but not limited to software source code, documentation -+ source, and configuration files. -+ -+ "Object" form shall mean any form resulting from mechanical -+ transformation or translation of a Source form, including but -+ not limited to compiled object code, generated documentation, -+ and conversions to other media types. -+ -+ "Work" shall mean the work of authorship, whether in Source or -+ Object form, made available under the License, as indicated by a -+ copyright notice that is included in or attached to the work -+ (an example is provided in the Appendix below). -+ -+ "Derivative Works" shall mean any work, whether in Source or Object -+ form, that is based on (or derived from) the Work and for which the -+ editorial revisions, annotations, elaborations, or other modifications -+ represent, as a whole, an original work of authorship. For the purposes -+ of this License, Derivative Works shall not include works that remain -+ separable from, or merely link (or bind by name) to the interfaces of, -+ the Work and Derivative Works thereof. -+ -+ "Contribution" shall mean any work of authorship, including -+ the original version of the Work and any modifications or additions -+ to that Work or Derivative Works thereof, that is intentionally -+ submitted to Licensor for inclusion in the Work by the copyright owner -+ or by an individual or Legal Entity authorized to submit on behalf of -+ the copyright owner. For the purposes of this definition, "submitted" -+ means any form of electronic, verbal, or written communication sent -+ to the Licensor or its representatives, including but not limited to -+ communication on electronic mailing lists, source code control systems, -+ and issue tracking systems that are managed by, or on behalf of, the -+ Licensor for the purpose of discussing and improving the Work, but -+ excluding communication that is conspicuously marked or otherwise -+ designated in writing by the copyright owner as "Not a Contribution." -+ -+ "Contributor" shall mean Licensor and any individual or Legal Entity -+ on behalf of whom a Contribution has been received by Licensor and -+ subsequently incorporated within the Work. -+ -+ 2. Grant of Copyright License. Subject to the terms and conditions of -+ this License, each Contributor hereby grants to You a perpetual, -+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable -+ copyright license to reproduce, prepare Derivative Works of, -+ publicly display, publicly perform, sublicense, and distribute the -+ Work and such Derivative Works in Source or Object form. -+ -+ 3. Grant of Patent License. Subject to the terms and conditions of -+ this License, each Contributor hereby grants to You a perpetual, -+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable -+ (except as stated in this section) patent license to make, have made, -+ use, offer to sell, sell, import, and otherwise transfer the Work, -+ where such license applies only to those patent claims licensable -+ by such Contributor that are necessarily infringed by their -+ Contribution(s) alone or by combination of their Contribution(s) -+ with the Work to which such Contribution(s) was submitted. If You -+ institute patent litigation against any entity (including a -+ cross-claim or counterclaim in a lawsuit) alleging that the Work -+ or a Contribution incorporated within the Work constitutes direct -+ or contributory patent infringement, then any patent licenses -+ granted to You under this License for that Work shall terminate -+ as of the date such litigation is filed. -+ -+ 4. Redistribution. You may reproduce and distribute copies of the -+ Work or Derivative Works thereof in any medium, with or without -+ modifications, and in Source or Object form, provided that You -+ meet the following conditions: -+ -+ (a) You must give any other recipients of the Work or -+ Derivative Works a copy of this License; and -+ -+ (b) You must cause any modified files to carry prominent notices -+ stating that You changed the files; and -+ -+ (c) You must retain, in the Source form of any Derivative Works -+ that You distribute, all copyright, patent, trademark, and -+ attribution notices from the Source form of the Work, -+ excluding those notices that do not pertain to any part of -+ the Derivative Works; and -+ -+ (d) If the Work includes a "NOTICE" text file as part of its -+ distribution, then any Derivative Works that You distribute must -+ include a readable copy of the attribution notices contained -+ within such NOTICE file, excluding those notices that do not -+ pertain to any part of the Derivative Works, in at least one -+ of the following places: within a NOTICE text file distributed -+ as part of the Derivative Works; within the Source form or -+ documentation, if provided along with the Derivative Works; or, -+ within a display generated by the Derivative Works, if and -+ wherever such third-party notices normally appear. The contents -+ of the NOTICE file are for informational purposes only and -+ do not modify the License. You may add Your own attribution -+ notices within Derivative Works that You distribute, alongside -+ or as an addendum to the NOTICE text from the Work, provided -+ that such additional attribution notices cannot be construed -+ as modifying the License. -+ -+ You may add Your own copyright statement to Your modifications and -+ may provide additional or different license terms and conditions -+ for use, reproduction, or distribution of Your modifications, or -+ for any such Derivative Works as a whole, provided Your use, -+ reproduction, and distribution of the Work otherwise complies with -+ the conditions stated in this License. -+ -+ 5. Submission of Contributions. Unless You explicitly state otherwise, -+ any Contribution intentionally submitted for inclusion in the Work -+ by You to the Licensor shall be under the terms and conditions of -+ this License, without any additional terms or conditions. -+ Notwithstanding the above, nothing herein shall supersede or modify -+ the terms of any separate license agreement you may have executed -+ with Licensor regarding such Contributions. -+ -+ 6. Trademarks. This License does not grant permission to use the trade -+ names, trademarks, service marks, or product names of the Licensor, -+ except as required for reasonable and customary use in describing the -+ origin of the Work and reproducing the content of the NOTICE file. -+ -+ 7. Disclaimer of Warranty. Unless required by applicable law or -+ agreed to in writing, Licensor provides the Work (and each -+ Contributor provides its Contributions) on an "AS IS" BASIS, -+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+ implied, including, without limitation, any warranties or conditions -+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -+ PARTICULAR PURPOSE. You are solely responsible for determining the -+ appropriateness of using or redistributing the Work and assume any -+ risks associated with Your exercise of permissions under this License. -+ -+ 8. Limitation of Liability. In no event and under no legal theory, -+ whether in tort (including negligence), contract, or otherwise, -+ unless required by applicable law (such as deliberate and grossly -+ negligent acts) or agreed to in writing, shall any Contributor be -+ liable to You for damages, including any direct, indirect, special, -+ incidental, or consequential damages of any character arising as a -+ result of this License or out of the use or inability to use the -+ Work (including but not limited to damages for loss of goodwill, -+ work stoppage, computer failure or malfunction, or any and all -+ other commercial damages or losses), even if such Contributor -+ has been advised of the possibility of such damages. -+ -+ 9. Accepting Warranty or Additional Liability. While redistributing -+ the Work or Derivative Works thereof, You may choose to offer, -+ and charge a fee for, acceptance of support, warranty, indemnity, -+ or other liability obligations and/or rights consistent with this -+ License. However, in accepting such obligations, You may act only -+ on Your own behalf and on Your sole responsibility, not on behalf -+ of any other Contributor, and only if You agree to indemnify, -+ defend, and hold each Contributor harmless for any liability -+ incurred by, or claims asserted against, such Contributor by reason -+ of your accepting any such warranty or additional liability. -+ -+ END OF TERMS AND CONDITIONS -+ -+ APPENDIX: How to apply the Apache License to your work. -+ -+ To apply the Apache License to your work, attach the following -+ boilerplate notice, with the fields enclosed by brackets "[]" -+ replaced with your own identifying information. (Don't include -+ the brackets!) The text should be enclosed in the appropriate -+ comment syntax for the file format. We also recommend that a -+ file or class name and description of purpose be included on the -+ same "printed page" as the copyright notice for easier -+ identification within third-party archives. -+ -+ Copyright [yyyy] [name of copyright owner] -+ -+ 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 -+ -+ http://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. -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/README.md -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/README.md -@@ -0,0 +1,3 @@ -+# Validation helpers [![Build Status](https://ci.vmware.run/api/badges/go-openapi/validate/status.svg)](https://ci.vmware.run/go-openapi/validate) [![Coverage](https://coverage.vmware.run/badges/go-openapi/validate/coverage.svg)](https://coverage.vmware.run/go-openapi/validate) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) -+ -+[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/validate/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/validate?status.svg)](http://godoc.org/github.com/go-openapi/validate) -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/formats.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/formats.go -@@ -0,0 +1,69 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "reflect" -+ -+ "github.com/go-openapi/spec" -+ "github.com/go-openapi/strfmt" -+) -+ -+type formatValidator struct { -+ Format string -+ Path string -+ In string -+ KnownFormats strfmt.Registry -+} -+ -+func (f *formatValidator) SetPath(path string) { -+ f.Path = path -+} -+ -+func (f *formatValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ doit := func() bool { -+ if source == nil { -+ return false -+ } -+ switch source.(type) { -+ case *spec.Items: -+ it := source.(*spec.Items) -+ return kind == reflect.String && f.KnownFormats.ContainsName(it.Format) -+ case *spec.Parameter: -+ par := source.(*spec.Parameter) -+ return kind == reflect.String && f.KnownFormats.ContainsName(par.Format) -+ case *spec.Schema: -+ sch := source.(*spec.Schema) -+ return kind == reflect.String && f.KnownFormats.ContainsName(sch.Format) -+ } -+ return false -+ } -+ r := doit() -+ // fmt.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", f.Path, r, source, kind) -+ return r -+} -+ -+func (f *formatValidator) Validate(val interface{}) *Result { -+ result := new(Result) -+ -+ if err := FormatOf(f.Path, f.In, f.Format, val.(string), f.KnownFormats); err != nil { -+ result.AddErrors(err) -+ } -+ -+ if result.HasErrors() { -+ return result -+ } -+ return nil -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/object_validator.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/object_validator.go -@@ -0,0 +1,151 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "reflect" -+ "regexp" -+ -+ "github.com/go-openapi/errors" -+ "github.com/go-openapi/spec" -+ "github.com/go-openapi/strfmt" -+) -+ -+type objectValidator struct { -+ Path string -+ In string -+ MaxProperties *int64 -+ MinProperties *int64 -+ Required []string -+ Properties map[string]spec.Schema -+ AdditionalProperties *spec.SchemaOrBool -+ PatternProperties map[string]spec.Schema -+ Root interface{} -+ KnownFormats strfmt.Registry -+} -+ -+func (o *objectValidator) SetPath(path string) { -+ o.Path = path -+} -+ -+func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ // TODO: this should also work for structs -+ // there is a problem in the type validator where it will be unhappy about null values -+ // so that requires more testing -+ r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct) -+ //fmt.Printf("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind) -+ return r -+} -+ -+func (o *objectValidator) Validate(data interface{}) *Result { -+ val := data.(map[string]interface{}) -+ numKeys := int64(len(val)) -+ -+ if o.MinProperties != nil && numKeys < *o.MinProperties { -+ return sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties)) -+ } -+ if o.MaxProperties != nil && numKeys > *o.MaxProperties { -+ return sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties)) -+ } -+ -+ res := new(Result) -+ if len(o.Required) > 0 { -+ for _, k := range o.Required { -+ if _, ok := val[k]; !ok { -+ res.AddErrors(errors.Required(o.Path+"."+k, o.In)) -+ continue -+ } -+ } -+ } -+ -+ if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows { -+ for k := range val { -+ _, regularProperty := o.Properties[k] -+ matched := false -+ -+ for pk := range o.PatternProperties { -+ if matches, _ := regexp.MatchString(pk, k); matches { -+ matched = true -+ break -+ } -+ } -+ if !regularProperty && k != "$schema" && k != "id" && !matched { -+ res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k)) -+ } -+ } -+ } else { -+ for key, value := range val { -+ _, regularProperty := o.Properties[key] -+ matched, succeededOnce, _ := o.validatePatternProperty(key, value, res) -+ if !(regularProperty || matched || succeededOnce) { -+ if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil { -+ res.Merge(NewSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats).Validate(value)) -+ } else if regularProperty && !(matched || succeededOnce) { -+ res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key)) -+ } -+ } -+ } -+ } -+ -+ for pName, pSchema := range o.Properties { -+ rName := pName -+ if o.Path != "" { -+ rName = o.Path + "." + pName -+ } -+ -+ if v, ok := val[pName]; ok { -+ res.Merge(NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats).Validate(v)) -+ } -+ } -+ -+ for key, value := range val { -+ _, regularProperty := o.Properties[key] -+ matched, succeededOnce, patterns := o.validatePatternProperty(key, value, res) -+ if !regularProperty && (matched || succeededOnce) { -+ for _, pName := range patterns { -+ if v, ok := o.PatternProperties[pName]; ok { -+ res.Merge(NewSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats).Validate(value)) -+ } -+ } -+ } -+ } -+ return res -+} -+ -+func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) { -+ matched := false -+ succeededOnce := false -+ var patterns []string -+ -+ for k, schema := range o.PatternProperties { -+ if match, _ := regexp.MatchString(k, key); match { -+ patterns = append(patterns, k) -+ matched = true -+ validator := NewSchemaValidator(&schema, o.Root, o.Path+"."+key, o.KnownFormats) -+ -+ res := validator.Validate(value) -+ result.Merge(res) -+ if res.IsValid() { -+ succeededOnce = true -+ } -+ } -+ } -+ -+ if succeededOnce { -+ result.Inc() -+ } -+ -+ return matched, succeededOnce, patterns -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/result.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/result.go -@@ -0,0 +1,61 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import "github.com/go-openapi/errors" -+ -+// Result represents a validation result -+type Result struct { -+ Errors []error -+ MatchCount int -+} -+ -+// Merge merges this result with the other one, preserving match counts etc -+func (r *Result) Merge(other *Result) *Result { -+ if other == nil { -+ return r -+ } -+ r.AddErrors(other.Errors...) -+ r.MatchCount += other.MatchCount -+ return r -+} -+ -+// AddErrors adds errors to this validation result -+func (r *Result) AddErrors(errors ...error) { -+ r.Errors = append(r.Errors, errors...) -+} -+ -+// IsValid returns true when this result is valid -+func (r *Result) IsValid() bool { -+ return len(r.Errors) == 0 -+} -+ -+// HasErrors returns true when this result is invalid -+func (r *Result) HasErrors() bool { -+ return !r.IsValid() -+} -+ -+// Inc increments the match count -+func (r *Result) Inc() { -+ r.MatchCount++ -+} -+ -+// AsError renders this result as an error interface -+func (r *Result) AsError() error { -+ if r.IsValid() { -+ return nil -+ } -+ return errors.CompositeValidationError(r.Errors...) -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/schema.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/schema.go -@@ -0,0 +1,193 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "reflect" -+ -+ "github.com/go-openapi/spec" -+ "github.com/go-openapi/strfmt" -+ "github.com/go-openapi/swag" -+) -+ -+var specSchemaType = reflect.TypeOf(&spec.Schema{}) -+var specParameterType = reflect.TypeOf(&spec.Parameter{}) -+var specItemsType = reflect.TypeOf(&spec.Items{}) -+var specHeaderType = reflect.TypeOf(&spec.Header{}) -+ -+// SchemaValidator like param validator but for a full json schema -+type SchemaValidator struct { -+ Path string -+ in string -+ Schema *spec.Schema -+ validators []valueValidator -+ Root interface{} -+ KnownFormats strfmt.Registry -+} -+ -+// NewSchemaValidator creates a new schema validator -+func NewSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry) *SchemaValidator { -+ if schema == nil { -+ return nil -+ } -+ -+ if rootSchema == nil { -+ rootSchema = schema -+ } -+ -+ if schema.ID != "" || schema.Ref.String() != "" || schema.Ref.IsRoot() { -+ err := spec.ExpandSchema(schema, rootSchema, nil) -+ if err != nil { -+ panic(err) -+ } -+ } -+ s := SchemaValidator{Path: root, in: "body", Schema: schema, Root: rootSchema, KnownFormats: formats} -+ s.validators = []valueValidator{ -+ s.typeValidator(), -+ s.schemaPropsValidator(), -+ s.stringValidator(), -+ s.formatValidator(), -+ s.numberValidator(), -+ s.sliceValidator(), -+ s.commonValidator(), -+ s.objectValidator(), -+ } -+ return &s -+} -+ -+// SetPath sets the path for this schema valdiator -+func (s *SchemaValidator) SetPath(path string) { -+ s.Path = path -+} -+ -+// Applies returns true when this schema validator applies -+func (s *SchemaValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ _, ok := source.(*spec.Schema) -+ return ok -+} -+ -+// Validate validates the data against the schema -+func (s *SchemaValidator) Validate(data interface{}) *Result { -+ if data == nil { -+ v := s.validators[0].Validate(data) -+ v.Merge(s.validators[6].Validate(data)) -+ return v -+ } -+ result := new(Result) -+ -+ tpe := reflect.TypeOf(data) -+ kind := tpe.Kind() -+ for kind == reflect.Ptr { -+ tpe = tpe.Elem() -+ kind = tpe.Kind() -+ } -+ d := data -+ if kind == reflect.Struct { -+ d = swag.ToDynamicJSON(data) -+ } -+ -+ for _, v := range s.validators { -+ if !v.Applies(s.Schema, kind) { -+ continue -+ } -+ -+ err := v.Validate(d) -+ result.Merge(err) -+ result.Inc() -+ } -+ result.Inc() -+ return result -+} -+ -+func (s *SchemaValidator) typeValidator() valueValidator { -+ return &typeValidator{Type: s.Schema.Type, Format: s.Schema.Format, In: s.in, Path: s.Path} -+} -+ -+func (s *SchemaValidator) commonValidator() valueValidator { -+ return &basicCommonValidator{ -+ Path: s.Path, -+ In: s.in, -+ Default: s.Schema.Default, -+ Enum: s.Schema.Enum, -+ } -+} -+ -+func (s *SchemaValidator) sliceValidator() valueValidator { -+ return &schemaSliceValidator{ -+ Path: s.Path, -+ In: s.in, -+ MaxItems: s.Schema.MaxItems, -+ MinItems: s.Schema.MinItems, -+ UniqueItems: s.Schema.UniqueItems, -+ AdditionalItems: s.Schema.AdditionalItems, -+ Items: s.Schema.Items, -+ Root: s.Root, -+ KnownFormats: s.KnownFormats, -+ } -+} -+ -+func (s *SchemaValidator) numberValidator() valueValidator { -+ return &numberValidator{ -+ Path: s.Path, -+ In: s.in, -+ Default: s.Schema.Default, -+ MultipleOf: s.Schema.MultipleOf, -+ Maximum: s.Schema.Maximum, -+ ExclusiveMaximum: s.Schema.ExclusiveMaximum, -+ Minimum: s.Schema.Minimum, -+ ExclusiveMinimum: s.Schema.ExclusiveMinimum, -+ } -+} -+ -+func (s *SchemaValidator) stringValidator() valueValidator { -+ return &stringValidator{ -+ Path: s.Path, -+ In: s.in, -+ Default: s.Schema.Default, -+ MaxLength: s.Schema.MaxLength, -+ MinLength: s.Schema.MinLength, -+ Pattern: s.Schema.Pattern, -+ } -+} -+ -+func (s *SchemaValidator) formatValidator() valueValidator { -+ return &formatValidator{ -+ Path: s.Path, -+ In: s.in, -+ //Default: s.Schema.Default, -+ Format: s.Schema.Format, -+ KnownFormats: s.KnownFormats, -+ } -+} -+ -+func (s *SchemaValidator) schemaPropsValidator() valueValidator { -+ sch := s.Schema -+ return newSchemaPropsValidator(s.Path, s.in, sch.AllOf, sch.OneOf, sch.AnyOf, sch.Not, sch.Dependencies, s.Root, s.KnownFormats) -+} -+ -+func (s *SchemaValidator) objectValidator() valueValidator { -+ return &objectValidator{ -+ Path: s.Path, -+ In: s.in, -+ MaxProperties: s.Schema.MaxProperties, -+ MinProperties: s.Schema.MinProperties, -+ Required: s.Schema.Required, -+ Properties: s.Schema.Properties, -+ AdditionalProperties: s.Schema.AdditionalProperties, -+ PatternProperties: s.Schema.PatternProperties, -+ Root: s.Root, -+ KnownFormats: s.KnownFormats, -+ } -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/schema_props.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/schema_props.go -@@ -0,0 +1,182 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "reflect" -+ -+ "github.com/go-openapi/errors" -+ "github.com/go-openapi/spec" -+ "github.com/go-openapi/strfmt" -+) -+ -+type schemaPropsValidator struct { -+ Path string -+ In string -+ AllOf []spec.Schema -+ OneOf []spec.Schema -+ AnyOf []spec.Schema -+ Not *spec.Schema -+ Dependencies spec.Dependencies -+ anyOfValidators []SchemaValidator -+ allOfValidators []SchemaValidator -+ oneOfValidators []SchemaValidator -+ notValidator *SchemaValidator -+ Root interface{} -+ KnownFormats strfmt.Registry -+} -+ -+func (s *schemaPropsValidator) SetPath(path string) { -+ s.Path = path -+} -+ -+func newSchemaPropsValidator(path string, in string, allOf, oneOf, anyOf []spec.Schema, not *spec.Schema, deps spec.Dependencies, root interface{}, formats strfmt.Registry) *schemaPropsValidator { -+ var anyValidators []SchemaValidator -+ for _, v := range anyOf { -+ anyValidators = append(anyValidators, *NewSchemaValidator(&v, root, path, formats)) -+ } -+ var allValidators []SchemaValidator -+ for _, v := range allOf { -+ allValidators = append(allValidators, *NewSchemaValidator(&v, root, path, formats)) -+ } -+ var oneValidators []SchemaValidator -+ for _, v := range oneOf { -+ oneValidators = append(oneValidators, *NewSchemaValidator(&v, root, path, formats)) -+ } -+ -+ var notValidator *SchemaValidator -+ if not != nil { -+ notValidator = NewSchemaValidator(not, root, path, formats) -+ } -+ -+ return &schemaPropsValidator{ -+ Path: path, -+ In: in, -+ AllOf: allOf, -+ OneOf: oneOf, -+ AnyOf: anyOf, -+ Not: not, -+ Dependencies: deps, -+ anyOfValidators: anyValidators, -+ allOfValidators: allValidators, -+ oneOfValidators: oneValidators, -+ notValidator: notValidator, -+ Root: root, -+ KnownFormats: formats, -+ } -+} -+ -+func (s *schemaPropsValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ r := reflect.TypeOf(source) == specSchemaType -+ // fmt.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind) -+ return r -+} -+ -+func (s *schemaPropsValidator) Validate(data interface{}) *Result { -+ mainResult := new(Result) -+ if len(s.anyOfValidators) > 0 { -+ var bestFailures *Result -+ succeededOnce := false -+ for _, anyOfSchema := range s.anyOfValidators { -+ result := anyOfSchema.Validate(data) -+ if result.IsValid() { -+ bestFailures = nil -+ succeededOnce = true -+ break -+ } -+ if bestFailures == nil || result.MatchCount > bestFailures.MatchCount { -+ bestFailures = result -+ } -+ } -+ -+ if !succeededOnce { -+ mainResult.AddErrors(errors.New(422, "must validate at least one schema (anyOf)")) -+ } -+ if bestFailures != nil { -+ mainResult.Merge(bestFailures) -+ } -+ } -+ -+ if len(s.oneOfValidators) > 0 { -+ var bestFailures *Result -+ validated := 0 -+ -+ for _, oneOfSchema := range s.oneOfValidators { -+ result := oneOfSchema.Validate(data) -+ if result.IsValid() { -+ validated++ -+ bestFailures = nil -+ continue -+ } -+ if validated == 0 && (bestFailures == nil || result.MatchCount > bestFailures.MatchCount) { -+ bestFailures = result -+ } -+ } -+ -+ if validated != 1 { -+ mainResult.AddErrors(errors.New(422, "must validate one and only one schema (oneOf)")) -+ if bestFailures != nil { -+ mainResult.Merge(bestFailures) -+ } -+ } -+ } -+ -+ if len(s.allOfValidators) > 0 { -+ validated := 0 -+ -+ for _, allOfSchema := range s.allOfValidators { -+ result := allOfSchema.Validate(data) -+ if result.IsValid() { -+ validated++ -+ } -+ mainResult.Merge(result) -+ } -+ -+ if validated != len(s.allOfValidators) { -+ mainResult.AddErrors(errors.New(422, "must validate all the schemas (allOf)")) -+ } -+ } -+ -+ if s.notValidator != nil { -+ result := s.notValidator.Validate(data) -+ if result.IsValid() { -+ mainResult.AddErrors(errors.New(422, "must not validate the schema (not)")) -+ } -+ } -+ -+ if s.Dependencies != nil && len(s.Dependencies) > 0 && reflect.TypeOf(data).Kind() == reflect.Map { -+ val := data.(map[string]interface{}) -+ for key := range val { -+ if dep, ok := s.Dependencies[key]; ok { -+ -+ if dep.Schema != nil { -+ mainResult.Merge(NewSchemaValidator(dep.Schema, s.Root, s.Path+"."+key, s.KnownFormats).Validate(data)) -+ continue -+ } -+ -+ if len(dep.Property) > 0 { -+ for _, depKey := range dep.Property { -+ if _, ok := val[depKey]; !ok { -+ mainResult.AddErrors(errors.New(422, "has a dependency on %s", depKey)) -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ mainResult.Inc() -+ return mainResult -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/slice_validator.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/slice_validator.go -@@ -0,0 +1,103 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "fmt" -+ "reflect" -+ -+ "github.com/go-openapi/errors" -+ "github.com/go-openapi/spec" -+ "github.com/go-openapi/strfmt" -+) -+ -+type schemaSliceValidator struct { -+ Path string -+ In string -+ MaxItems *int64 -+ MinItems *int64 -+ UniqueItems bool -+ AdditionalItems *spec.SchemaOrBool -+ Items *spec.SchemaOrArray -+ Root interface{} -+ KnownFormats strfmt.Registry -+} -+ -+func (s *schemaSliceValidator) SetPath(path string) { -+ s.Path = path -+} -+ -+func (s *schemaSliceValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ _, ok := source.(*spec.Schema) -+ r := ok && kind == reflect.Slice -+ return r -+} -+ -+func (s *schemaSliceValidator) Validate(data interface{}) *Result { -+ result := new(Result) -+ if data == nil { -+ return result -+ } -+ val := reflect.ValueOf(data) -+ size := val.Len() -+ -+ if s.Items != nil && s.Items.Schema != nil { -+ validator := NewSchemaValidator(s.Items.Schema, s.Root, s.Path, s.KnownFormats) -+ for i := 0; i < size; i++ { -+ validator.SetPath(fmt.Sprintf("%s.%d", s.Path, i)) -+ value := val.Index(i) -+ result.Merge(validator.Validate(value.Interface())) -+ } -+ } -+ -+ itemsSize := int64(0) -+ if s.Items != nil && len(s.Items.Schemas) > 0 { -+ itemsSize = int64(len(s.Items.Schemas)) -+ for i := int64(0); i < itemsSize; i++ { -+ validator := NewSchemaValidator(&s.Items.Schemas[i], s.Root, fmt.Sprintf("%s.%d", s.Path, i), s.KnownFormats) -+ result.Merge(validator.Validate(val.Index(int(i)).Interface())) -+ } -+ -+ } -+ if s.AdditionalItems != nil && itemsSize < int64(size) { -+ if s.Items != nil && len(s.Items.Schemas) > 0 && !s.AdditionalItems.Allows { -+ result.AddErrors(errors.New(422, "array doesn't allow for additional items")) -+ } -+ if s.AdditionalItems.Schema != nil { -+ for i := itemsSize; i < (int64(size)-itemsSize)+1; i++ { -+ validator := NewSchemaValidator(s.AdditionalItems.Schema, s.Root, fmt.Sprintf("%s.%d", s.Path, i), s.KnownFormats) -+ result.Merge(validator.Validate(val.Index(int(i)).Interface())) -+ } -+ } -+ } -+ -+ if s.MinItems != nil { -+ if err := MinItems(s.Path, s.In, int64(size), *s.MinItems); err != nil { -+ result.AddErrors(err) -+ } -+ } -+ if s.MaxItems != nil { -+ if err := MaxItems(s.Path, s.In, int64(size), *s.MaxItems); err != nil { -+ result.AddErrors(err) -+ } -+ } -+ if s.UniqueItems { -+ if err := UniqueItems(s.Path, s.In, val.Interface()); err != nil { -+ result.AddErrors(err) -+ } -+ } -+ result.Inc() -+ return result -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/spec.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/spec.go -@@ -0,0 +1,792 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "encoding/json" -+ "fmt" -+ "log" -+ "regexp" -+ "strings" -+ -+ "github.com/go-openapi/analysis" -+ "github.com/go-openapi/errors" -+ "github.com/go-openapi/jsonpointer" -+ "github.com/go-openapi/loads" -+ "github.com/go-openapi/spec" -+ "github.com/go-openapi/strfmt" -+) -+ -+// Spec validates a spec document -+// It validates the spec json against the json schema for swagger -+// and then validates a number of extra rules that can't be expressed in json schema: -+// -+// - definition can't declare a property that's already defined by one of its ancestors -+// - definition's ancestor can't be a descendant of the same model -+// - each api path should be non-verbatim (account for path param names) unique per method -+// - each security reference should contain only unique scopes -+// - each security scope in a security definition should be unique -+// - each path parameter should correspond to a parameter placeholder and vice versa -+// - each referencable definition must have references -+// - each definition property listed in the required array must be defined in the properties of the model -+// - each parameter should have a unique `name` and `type` combination -+// - each operation should have only 1 parameter of type body -+// - each reference must point to a valid object -+// - every default value that is specified must validate against the schema for that property -+// - items property is required for all schemas/definitions of type `array` -+func Spec(doc *loads.Document, formats strfmt.Registry) error { -+ errs, _ /*warns*/ := NewSpecValidator(doc.Schema(), formats).Validate(doc) -+ if errs.HasErrors() { -+ return errors.CompositeValidationError(errs.Errors...) -+ } -+ return nil -+} -+ -+// AgainstSchema validates the specified data with the provided schema, when no schema -+// is provided it uses the json schema as default -+func AgainstSchema(schema *spec.Schema, data interface{}, formats strfmt.Registry) error { -+ res := NewSchemaValidator(schema, nil, "", formats).Validate(data) -+ if res.HasErrors() { -+ return errors.CompositeValidationError(res.Errors...) -+ } -+ return nil -+} -+ -+// SpecValidator validates a swagger spec -+type SpecValidator struct { -+ schema *spec.Schema // swagger 2.0 schema -+ spec *loads.Document -+ analyzer *analysis.Spec -+ expanded *loads.Document -+ KnownFormats strfmt.Registry -+} -+ -+// NewSpecValidator creates a new swagger spec validator instance -+func NewSpecValidator(schema *spec.Schema, formats strfmt.Registry) *SpecValidator { -+ return &SpecValidator{ -+ schema: schema, -+ KnownFormats: formats, -+ } -+} -+ -+// Validate validates the swagger spec -+func (s *SpecValidator) Validate(data interface{}) (errs *Result, warnings *Result) { -+ var sd *loads.Document -+ -+ switch v := data.(type) { -+ case *loads.Document: -+ sd = v -+ } -+ if sd == nil { -+ errs = sErr(errors.New(500, "spec validator can only validate spec.Document objects")) -+ return -+ } -+ s.spec = sd -+ s.analyzer = analysis.New(sd.Spec()) -+ -+ errs = new(Result) -+ warnings = new(Result) -+ -+ schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats) -+ var obj interface{} -+ if err := json.Unmarshal(sd.Raw(), &obj); err != nil { -+ errs.AddErrors(err) -+ return -+ } -+ errs.Merge(schv.Validate(obj)) // error - -+ if errs.HasErrors() { -+ return // no point in continuing -+ } -+ -+ errs.Merge(s.validateReferencesValid()) // error - -+ if errs.HasErrors() { -+ return // no point in continuing -+ } -+ -+ errs.Merge(s.validateDuplicateOperationIDs()) -+ errs.Merge(s.validateDuplicatePropertyNames()) // error - -+ errs.Merge(s.validateParameters()) // error - -+ errs.Merge(s.validateItems()) // error - -+ errs.Merge(s.validateRequiredDefinitions()) // error - -+ errs.Merge(s.validateDefaultValueValidAgainstSchema()) // error - -+ errs.Merge(s.validateExamplesValidAgainstSchema()) // error - -+ errs.Merge(s.validateNonEmptyPathParamNames()) -+ -+ warnings.Merge(s.validateUniqueSecurityScopes()) // warning -+ warnings.Merge(s.validateReferenced()) // warning -+ -+ return -+} -+ -+func (s *SpecValidator) validateNonEmptyPathParamNames() *Result { -+ res := new(Result) -+ for k := range s.spec.Spec().Paths.Paths { -+ if strings.Contains(k, "{}") { -+ res.AddErrors(errors.New(422, "%q contains an empty path parameter", k)) -+ } -+ } -+ return res -+} -+ -+func (s *SpecValidator) validateDuplicateOperationIDs() *Result { -+ res := new(Result) -+ known := make(map[string]int) -+ for _, v := range s.analyzer.OperationIDs() { -+ if v != "" { -+ known[v]++ -+ } -+ } -+ for k, v := range known { -+ if v > 1 { -+ res.AddErrors(errors.New(422, "%q is defined %d times", k, v)) -+ } -+ } -+ return res -+} -+ -+type dupProp struct { -+ Name string -+ Definition string -+} -+ -+func (s *SpecValidator) validateDuplicatePropertyNames() *Result { -+ // definition can't declare a property that's already defined by one of its ancestors -+ res := new(Result) -+ for k, sch := range s.spec.Spec().Definitions { -+ if len(sch.AllOf) == 0 { -+ continue -+ } -+ -+ knownanc := map[string]struct{}{ -+ "#/definitions/" + k: struct{}{}, -+ } -+ -+ ancs := s.validateCircularAncestry(k, sch, knownanc) -+ if len(ancs) > 0 { -+ res.AddErrors(errors.New(422, "definition %q has circular ancestry: %v", k, ancs)) -+ return res -+ } -+ -+ knowns := make(map[string]struct{}) -+ dups := s.validateSchemaPropertyNames(k, sch, knowns) -+ if len(dups) > 0 { -+ var pns []string -+ for _, v := range dups { -+ pns = append(pns, v.Definition+"."+v.Name) -+ } -+ res.AddErrors(errors.New(422, "definition %q contains duplicate properties: %v", k, pns)) -+ } -+ -+ } -+ return res -+} -+ -+func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) []dupProp { -+ var dups []dupProp -+ -+ schn := nm -+ schc := &sch -+ for schc.Ref.String() != "" { -+ // gather property names -+ reso, err := spec.ResolveRef(s.spec.Spec(), &schc.Ref) -+ if err != nil { -+ panic(err) -+ } -+ schc = reso -+ schn = sch.Ref.String() -+ } -+ -+ if len(schc.AllOf) > 0 { -+ for _, chld := range schc.AllOf { -+ dups = append(dups, s.validateSchemaPropertyNames(schn, chld, knowns)...) -+ } -+ return dups -+ } -+ -+ for k := range schc.Properties { -+ _, ok := knowns[k] -+ if ok { -+ dups = append(dups, dupProp{Name: k, Definition: schn}) -+ } else { -+ knowns[k] = struct{}{} -+ } -+ } -+ -+ return dups -+} -+ -+func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) []string { -+ if sch.Ref.String() == "" && len(sch.AllOf) == 0 { -+ return nil -+ } -+ var ancs []string -+ -+ schn := nm -+ schc := &sch -+ for schc.Ref.String() != "" { -+ reso, err := spec.ResolveRef(s.spec.Spec(), &schc.Ref) -+ if err != nil { -+ panic(err) -+ } -+ schc = reso -+ schn = sch.Ref.String() -+ } -+ -+ if schn != nm && schn != "" { -+ if _, ok := knowns[schn]; ok { -+ ancs = append(ancs, schn) -+ } -+ knowns[schn] = struct{}{} -+ -+ if len(ancs) > 0 { -+ return ancs -+ } -+ } -+ -+ if len(schc.AllOf) > 0 { -+ for _, chld := range schc.AllOf { -+ if chld.Ref.String() != "" || len(chld.AllOf) > 0 { -+ ancs = append(ancs, s.validateCircularAncestry(schn, chld, knowns)...) -+ if len(ancs) > 0 { -+ return ancs -+ } -+ } -+ } -+ } -+ -+ return ancs -+} -+ -+func (s *SpecValidator) validateItems() *Result { -+ // validate parameter, items, schema and response objects for presence of item if type is array -+ res := new(Result) -+ -+ // TODO: implement support for lookups of refs -+ for method, pi := range s.analyzer.Operations() { -+ for path, op := range pi { -+ for _, param := range s.analyzer.ParamsFor(method, path) { -+ if param.TypeName() == "array" && param.ItemsTypeName() == "" { -+ res.AddErrors(errors.New(422, "param %q for %q is a collection without an element type", param.Name, op.ID)) -+ continue -+ } -+ if param.In != "body" { -+ if param.Items != nil { -+ items := param.Items -+ for items.TypeName() == "array" { -+ if items.ItemsTypeName() == "" { -+ res.AddErrors(errors.New(422, "param %q for %q is a collection without an element type", param.Name, op.ID)) -+ break -+ } -+ items = items.Items -+ } -+ } -+ } else { -+ if err := s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID); err != nil { -+ res.AddErrors(err) -+ } -+ } -+ } -+ -+ var responses []spec.Response -+ if op.Responses != nil { -+ if op.Responses.Default != nil { -+ responses = append(responses, *op.Responses.Default) -+ } -+ for _, v := range op.Responses.StatusCodeResponses { -+ responses = append(responses, v) -+ } -+ } -+ -+ for _, resp := range responses { -+ for hn, hv := range resp.Headers { -+ if hv.TypeName() == "array" && hv.ItemsTypeName() == "" { -+ res.AddErrors(errors.New(422, "header %q for %q is a collection without an element type", hn, op.ID)) -+ } -+ } -+ if resp.Schema != nil { -+ if err := s.validateSchemaItems(*resp.Schema, "response body", op.ID); err != nil { -+ res.AddErrors(err) -+ } -+ } -+ } -+ } -+ } -+ return res -+} -+ -+func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) error { -+ if !schema.Type.Contains("array") { -+ return nil -+ } -+ -+ if schema.Items == nil || schema.Items.Len() == 0 { -+ return errors.New(422, "%s for %q is a collection without an element type", prefix, opID) -+ } -+ -+ schemas := schema.Items.Schemas -+ if schema.Items.Schema != nil { -+ schemas = []spec.Schema{*schema.Items.Schema} -+ } -+ for _, sch := range schemas { -+ if err := s.validateSchemaItems(sch, prefix, opID); err != nil { -+ return err -+ } -+ } -+ return nil -+} -+ -+func (s *SpecValidator) validateUniqueSecurityScopes() *Result { -+ // Each authorization/security reference should contain only unique scopes. -+ // (Example: For an oauth2 authorization/security requirement, when listing the required scopes, -+ // each scope should only be listed once.) -+ -+ return nil -+} -+ -+func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result { -+ // Each defined operation path parameters must correspond to a named element in the API's path pattern. -+ // (For example, you cannot have a path parameter named id for the following path /pets/{petId} but you must have a path parameter named petId.) -+ res := new(Result) -+ for _, l := range fromPath { -+ var matched bool -+ for _, r := range fromOperation { -+ if l == "{"+r+"}" { -+ matched = true -+ break -+ } -+ } -+ if !matched { -+ res.Errors = append(res.Errors, errors.New(422, "path param %q has no parameter definition", l)) -+ } -+ } -+ -+ for _, p := range fromOperation { -+ var matched bool -+ for _, r := range fromPath { -+ if "{"+p+"}" == r { -+ matched = true -+ break -+ } -+ } -+ if !matched { -+ res.AddErrors(errors.New(422, "path param %q is not present in path %q", p, path)) -+ } -+ } -+ -+ return res -+} -+ -+func (s *SpecValidator) validateReferenced() *Result { -+ var res Result -+ res.Merge(s.validateReferencedParameters()) -+ res.Merge(s.validateReferencedResponses()) -+ res.Merge(s.validateReferencedDefinitions()) -+ return &res -+} -+ -+func (s *SpecValidator) validateReferencedParameters() *Result { -+ // Each referenceable definition must have references. -+ params := s.spec.Spec().Parameters -+ if len(params) == 0 { -+ return nil -+ } -+ -+ expected := make(map[string]struct{}) -+ for k := range params { -+ expected["#/parameters/"+jsonpointer.Escape(k)] = struct{}{} -+ } -+ for _, k := range s.analyzer.AllParameterReferences() { -+ if _, ok := expected[k]; ok { -+ delete(expected, k) -+ } -+ } -+ -+ if len(expected) == 0 { -+ return nil -+ } -+ var result Result -+ for k := range expected { -+ result.AddErrors(errors.New(422, "parameter %q is not used anywhere", k)) -+ } -+ return &result -+} -+ -+func (s *SpecValidator) validateReferencedResponses() *Result { -+ // Each referenceable definition must have references. -+ responses := s.spec.Spec().Responses -+ if len(responses) == 0 { -+ return nil -+ } -+ -+ expected := make(map[string]struct{}) -+ for k := range responses { -+ expected["#/responses/"+jsonpointer.Escape(k)] = struct{}{} -+ } -+ for _, k := range s.analyzer.AllResponseReferences() { -+ if _, ok := expected[k]; ok { -+ delete(expected, k) -+ } -+ } -+ -+ if len(expected) == 0 { -+ return nil -+ } -+ var result Result -+ for k := range expected { -+ result.AddErrors(errors.New(422, "response %q is not used anywhere", k)) -+ } -+ return &result -+} -+ -+func (s *SpecValidator) validateReferencedDefinitions() *Result { -+ // Each referenceable definition must have references. -+ defs := s.spec.Spec().Definitions -+ if len(defs) == 0 { -+ return nil -+ } -+ -+ expected := make(map[string]struct{}) -+ for k := range defs { -+ expected["#/definitions/"+jsonpointer.Escape(k)] = struct{}{} -+ } -+ for _, k := range s.analyzer.AllDefinitionReferences() { -+ if _, ok := expected[k]; ok { -+ delete(expected, k) -+ } -+ } -+ -+ if len(expected) == 0 { -+ return nil -+ } -+ var result Result -+ for k := range expected { -+ result.AddErrors(errors.New(422, "definition %q is not used anywhere", k)) -+ } -+ return &result -+} -+ -+func (s *SpecValidator) validateRequiredDefinitions() *Result { -+ // Each definition property listed in the required array must be defined in the properties of the model -+ res := new(Result) -+ for d, v := range s.spec.Spec().Definitions { -+ REQUIRED: -+ for _, pn := range v.Required { -+ if _, ok := v.Properties[pn]; ok { -+ continue -+ } -+ -+ for pp := range v.PatternProperties { -+ re := regexp.MustCompile(pp) -+ if re.MatchString(pn) { -+ continue REQUIRED -+ } -+ } -+ -+ if v.AdditionalProperties != nil { -+ if v.AdditionalProperties.Allows { -+ continue -+ } -+ if v.AdditionalProperties.Schema != nil { -+ continue -+ } -+ } -+ -+ res.AddErrors(errors.New(422, "%q is present in required but not defined as property in definition %q", pn, d)) -+ } -+ } -+ return res -+} -+ -+func (s *SpecValidator) validateParameters() *Result { -+ // each parameter should have a unique `name` and `type` combination -+ // each operation should have only 1 parameter of type body -+ // each api path should be non-verbatim (account for path param names) unique per method -+ res := new(Result) -+ for method, pi := range s.analyzer.Operations() { -+ knownPaths := make(map[string]string) -+ for path, op := range pi { -+ segments, params := parsePath(path) -+ knowns := make([]string, 0, len(segments)) -+ for _, s := range segments { -+ knowns = append(knowns, s) -+ } -+ var fromPath []string -+ for _, i := range params { -+ fromPath = append(fromPath, knowns[i]) -+ knowns[i] = "!" -+ } -+ knownPath := strings.Join(knowns, "/") -+ if orig, ok := knownPaths[knownPath]; ok { -+ res.AddErrors(errors.New(422, "path %s overlaps with %s", path, orig)) -+ } else { -+ knownPaths[knownPath] = path -+ } -+ -+ ptypes := make(map[string]map[string]struct{}) -+ var firstBodyParam string -+ sw := s.spec.Spec() -+ var paramNames []string -+ PARAMETERS: -+ for _, ppr := range op.Parameters { -+ pr := ppr -+ for pr.Ref.String() != "" { -+ obj, _, err := pr.Ref.GetPointer().Get(sw) -+ if err != nil { -+ log.Println(err) -+ res.AddErrors(err) -+ break PARAMETERS -+ } -+ pr = obj.(spec.Parameter) -+ } -+ pnames, ok := ptypes[pr.In] -+ if !ok { -+ pnames = make(map[string]struct{}) -+ ptypes[pr.In] = pnames -+ } -+ -+ _, ok = pnames[pr.Name] -+ if ok { -+ res.AddErrors(errors.New(422, "duplicate parameter name %q for %q in operation %q", pr.Name, pr.In, op.ID)) -+ } -+ pnames[pr.Name] = struct{}{} -+ } -+ -+ PARAMETERS2: -+ for _, ppr := range s.analyzer.ParamsFor(method, path) { -+ pr := ppr -+ for pr.Ref.String() != "" { -+ obj, _, err := pr.Ref.GetPointer().Get(sw) -+ if err != nil { -+ res.AddErrors(err) -+ break PARAMETERS2 -+ } -+ pr = obj.(spec.Parameter) -+ } -+ -+ if pr.In == "body" { -+ if firstBodyParam != "" { -+ res.AddErrors(errors.New(422, "operation %q has more than 1 body param (accepted: %q, dropped: %q)", op.ID, firstBodyParam, pr.Name)) -+ } -+ firstBodyParam = pr.Name -+ } -+ -+ if pr.In == "path" { -+ paramNames = append(paramNames, pr.Name) -+ } -+ } -+ res.Merge(s.validatePathParamPresence(path, fromPath, paramNames)) -+ } -+ } -+ return res -+} -+ -+func parsePath(path string) (segments []string, params []int) { -+ for i, p := range strings.Split(path, "/") { -+ segments = append(segments, p) -+ if len(p) > 0 && p[0] == '{' && p[len(p)-1] == '}' { -+ params = append(params, i) -+ } -+ } -+ return -+} -+ -+func (s *SpecValidator) validateReferencesValid() *Result { -+ // each reference must point to a valid object -+ res := new(Result) -+ for _, r := range s.analyzer.AllRefs() { -+ if !r.IsValidURI() { -+ res.AddErrors(errors.New(404, "invalid ref %q", r.String())) -+ } -+ } -+ if !res.HasErrors() { -+ exp, err := s.spec.Expanded() -+ if err != nil { -+ res.AddErrors(err) -+ } -+ s.expanded = exp -+ } -+ return res -+} -+ -+func (s *SpecValidator) validateResponseExample(path string, r *spec.Response) *Result { -+ res := new(Result) -+ if r.Ref.String() != "" { -+ nr, _, err := r.Ref.GetPointer().Get(s.spec.Spec()) -+ if err != nil { -+ res.AddErrors(err) -+ return res -+ } -+ rr := nr.(spec.Response) -+ return s.validateResponseExample(path, &rr) -+ } -+ -+ if r.Examples != nil { -+ if r.Schema != nil { -+ if example, ok := r.Examples["application/json"]; ok { -+ res.Merge(NewSchemaValidator(r.Schema, s.spec.Spec(), path, s.KnownFormats).Validate(example)) -+ } -+ -+ // TODO: validate other media types too -+ } -+ } -+ return res -+} -+ -+func (s *SpecValidator) validateExamplesValidAgainstSchema() *Result { -+ res := new(Result) -+ -+ for _, pathItem := range s.analyzer.Operations() { -+ for path, op := range pathItem { -+ if op.Responses.Default != nil { -+ dr := op.Responses.Default -+ res.Merge(s.validateResponseExample(path, dr)) -+ } -+ for _, r := range op.Responses.StatusCodeResponses { -+ res.Merge(s.validateResponseExample(path, &r)) -+ } -+ } -+ } -+ -+ return res -+} -+ -+func (s *SpecValidator) validateDefaultValueValidAgainstSchema() *Result { -+ // every default value that is specified must validate against the schema for that property -+ // headers, items, parameters, schema -+ -+ res := new(Result) -+ -+ for method, pathItem := range s.analyzer.Operations() { -+ for path, op := range pathItem { -+ // parameters -+ var hasForm, hasBody bool -+ PARAMETERS: -+ for _, pr := range s.analyzer.ParamsFor(method, path) { -+ // expand ref is necessary -+ param := pr -+ for param.Ref.String() != "" { -+ obj, _, err := param.Ref.GetPointer().Get(s.spec.Spec()) -+ if err != nil { -+ res.AddErrors(err) -+ break PARAMETERS -+ } -+ param = obj.(spec.Parameter) -+ } -+ if param.In == "formData" { -+ if hasBody && !hasForm { -+ res.AddErrors(errors.New(422, "operation %q has both formData and body parameters", op.ID)) -+ } -+ hasForm = true -+ } -+ if param.In == "body" { -+ if hasForm && !hasBody { -+ res.AddErrors(errors.New(422, "operation %q has both body and formData parameters", op.ID)) -+ } -+ hasBody = true -+ } -+ // check simple paramters first -+ if param.Default != nil && param.Schema == nil { -+ //fmt.Println(param.Name, "in", param.In, "has a default without a schema") -+ // check param valid -+ res.Merge(NewParamValidator(¶m, s.KnownFormats).Validate(param.Default)) -+ } -+ -+ if param.Items != nil { -+ res.Merge(s.validateDefaultValueItemsAgainstSchema(param.Name, param.In, ¶m, param.Items)) -+ } -+ -+ if param.Schema != nil { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(param.Name, param.In, param.Schema)) -+ } -+ } -+ -+ if op.Responses.Default != nil { -+ dr := op.Responses.Default -+ for nm, h := range dr.Headers { -+ if h.Default != nil { -+ res.Merge(NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Default)) -+ } -+ if h.Items != nil { -+ res.Merge(s.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items)) -+ } -+ } -+ } -+ for _, r := range op.Responses.StatusCodeResponses { -+ for nm, h := range r.Headers { -+ if h.Default != nil { -+ res.Merge(NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Default)) -+ } -+ if h.Items != nil { -+ res.Merge(s.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items)) -+ } -+ } -+ } -+ -+ } -+ } -+ -+ for nm, sch := range s.spec.Spec().Definitions { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("definitions.%s", nm), "body", &sch)) -+ } -+ -+ return res -+} -+ -+func (s *SpecValidator) validateDefaultValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result { -+ res := new(Result) -+ if schema != nil { -+ if schema.Default != nil { -+ res.Merge(NewSchemaValidator(schema, s.spec.Spec(), path, s.KnownFormats).Validate(schema.Default)) -+ } -+ if schema.Items != nil { -+ if schema.Items.Schema != nil { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(path+".items", in, schema.Items.Schema)) -+ } -+ for i, sch := range schema.Items.Schemas { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d]", path, i), in, &sch)) -+ } -+ } -+ if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalItems", path), in, schema.AdditionalItems.Schema)) -+ } -+ for propName, prop := range schema.Properties { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) -+ } -+ for propName, prop := range schema.PatternProperties { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) -+ } -+ if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalProperties", path), in, schema.AdditionalProperties.Schema)) -+ } -+ for i, aoSch := range schema.AllOf { -+ res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch)) -+ } -+ -+ } -+ return res -+} -+ -+func (s *SpecValidator) validateDefaultValueItemsAgainstSchema(path, in string, root interface{}, items *spec.Items) *Result { -+ res := new(Result) -+ if items != nil { -+ if items.Default != nil { -+ res.Merge(newItemsValidator(path, in, items, root, s.KnownFormats).Validate(0, items.Default)) -+ } -+ if items.Items != nil { -+ res.Merge(s.validateDefaultValueItemsAgainstSchema(path+"[0]", in, root, items.Items)) -+ } -+ } -+ return res -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/type.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/type.go -@@ -0,0 +1,160 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "reflect" -+ "strings" -+ -+ "github.com/go-openapi/errors" -+ "github.com/go-openapi/runtime" -+ "github.com/go-openapi/spec" -+ "github.com/go-openapi/strfmt" -+ "github.com/go-openapi/swag" -+) -+ -+type typeValidator struct { -+ Type spec.StringOrArray -+ Format string -+ In string -+ Path string -+} -+ -+var jsonTypeNames = map[string]struct{}{ -+ "array": struct{}{}, -+ "boolean": struct{}{}, -+ "integer": struct{}{}, -+ "null": struct{}{}, -+ "number": struct{}{}, -+ "object": struct{}{}, -+ "string": struct{}{}, -+} -+ -+func (t *typeValidator) schemaInfoForType(data interface{}) (string, string) { -+ switch data.(type) { -+ case []byte: -+ return "string", "byte" -+ case strfmt.Date, *strfmt.Date: -+ return "string", "date" -+ case strfmt.DateTime, *strfmt.DateTime: -+ return "string", "datetime" -+ case runtime.File, *runtime.File: -+ return "file", "" -+ case strfmt.URI, *strfmt.URI: -+ return "string", "uri" -+ case strfmt.Email, *strfmt.Email: -+ return "string", "email" -+ case strfmt.Hostname, *strfmt.Hostname: -+ return "string", "hostname" -+ case strfmt.IPv4, *strfmt.IPv4: -+ return "string", "ipv4" -+ case strfmt.IPv6, *strfmt.IPv6: -+ return "string", "ipv6" -+ case strfmt.UUID, *strfmt.UUID: -+ return "string", "uuid" -+ case strfmt.UUID3, *strfmt.UUID3: -+ return "string", "uuid3" -+ case strfmt.UUID4, *strfmt.UUID4: -+ return "string", "uuid4" -+ case strfmt.UUID5, *strfmt.UUID5: -+ return "string", "uuid5" -+ case strfmt.ISBN, *strfmt.ISBN: -+ return "string", "isbn" -+ case strfmt.ISBN10, *strfmt.ISBN10: -+ return "string", "isbn10" -+ case strfmt.ISBN13, *strfmt.ISBN13: -+ return "string", "isbn13" -+ case strfmt.CreditCard, *strfmt.CreditCard: -+ return "string", "creditcard" -+ case strfmt.SSN, *strfmt.SSN: -+ return "string", "ssn" -+ case strfmt.HexColor, *strfmt.HexColor: -+ return "string", "hexcolor" -+ case strfmt.RGBColor, *strfmt.RGBColor: -+ return "string", "rgbcolor" -+ default: -+ val := reflect.ValueOf(data) -+ tpe := val.Type() -+ switch tpe.Kind() { -+ case reflect.Bool: -+ return "boolean", "" -+ case reflect.String: -+ return "string", "" -+ case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32: -+ return "integer", "int32" -+ case reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64: -+ return "integer", "int64" -+ case reflect.Float32: -+ return "number", "float32" -+ case reflect.Float64: -+ return "number", "float64" -+ case reflect.Slice: -+ return "array", "" -+ case reflect.Map, reflect.Struct: -+ return "object", "" -+ case reflect.Interface: -+ // What to do here? -+ panic("dunno what to do here") -+ case reflect.Ptr: -+ return t.schemaInfoForType(reflect.Indirect(val).Interface()) -+ } -+ } -+ return "", "" -+} -+ -+func (t *typeValidator) SetPath(path string) { -+ t.Path = path -+} -+ -+func (t *typeValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ stpe := reflect.TypeOf(source) -+ r := (len(t.Type) > 0 || t.Format != "") && (stpe == specSchemaType || stpe == specParameterType || stpe == specHeaderType) -+ //fmt.Printf("type validator for %q applies %t for %T (kind: %v)\n", t.Path, r, source, kind) -+ return r -+} -+ -+func (t *typeValidator) Validate(data interface{}) *Result { -+ result := new(Result) -+ result.Inc() -+ if data == nil || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(data)), reflect.ValueOf(data)) { -+ if len(t.Type) > 0 && !t.Type.Contains("null") { // TODO: if a property is not required it also passes this -+ return sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), "null")) -+ } -+ return result -+ } -+ -+ // check if the type matches, should be used in every validator chain as first item -+ val := reflect.Indirect(reflect.ValueOf(data)) -+ kind := val.Kind() -+ -+ schType, format := t.schemaInfoForType(data) -+ //fmt.Println("path:", t.Path, "schType:", schType, "format:", format, "expType:", t.Type, "expFmt:", t.Format, "kind:", val.Kind().String()) -+ isLowerInt := t.Format == "int64" && format == "int32" -+ isLowerFloat := t.Format == "float64" && format == "float32" -+ isFloatInt := schType == "number" && swag.IsFloat64AJSONInteger(val.Float()) && t.Type.Contains("integer") -+ isIntFloat := schType == "integer" && t.Type.Contains("number") -+ -+ if kind != reflect.String && kind != reflect.Slice && t.Format != "" && !(t.Type.Contains(schType) || format == t.Format || isFloatInt || isIntFloat || isLowerInt || isLowerFloat) { -+ return sErr(errors.InvalidType(t.Path, t.In, t.Format, format)) -+ } -+ if !(t.Type.Contains("number") || t.Type.Contains("integer")) && t.Format != "" && (kind == reflect.String || kind == reflect.Slice) { -+ return result -+ } -+ -+ if !(t.Type.Contains(schType) || isFloatInt || isIntFloat) { -+ return sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), schType)) -+ } -+ return result -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/validator.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/validator.go -@@ -0,0 +1,573 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "fmt" -+ "reflect" -+ -+ "github.com/go-openapi/errors" -+ "github.com/go-openapi/spec" -+ "github.com/go-openapi/strfmt" -+) -+ -+// An EntityValidator is an interface for things that can validate entities -+type EntityValidator interface { -+ Validate(interface{}) *Result -+} -+ -+type valueValidator interface { -+ SetPath(path string) -+ Applies(interface{}, reflect.Kind) bool -+ Validate(interface{}) *Result -+} -+ -+type itemsValidator struct { -+ items *spec.Items -+ root interface{} -+ path string -+ in string -+ validators []valueValidator -+ KnownFormats strfmt.Registry -+} -+ -+func newItemsValidator(path, in string, items *spec.Items, root interface{}, formats strfmt.Registry) *itemsValidator { -+ iv := &itemsValidator{path: path, in: in, items: items, root: root, KnownFormats: formats} -+ iv.validators = []valueValidator{ -+ &typeValidator{ -+ Type: spec.StringOrArray([]string{items.Type}), -+ Format: items.Format, -+ In: in, -+ Path: path, -+ }, -+ iv.stringValidator(), -+ iv.formatValidator(), -+ iv.numberValidator(), -+ iv.sliceValidator(), -+ iv.commonValidator(), -+ } -+ return iv -+} -+ -+func (i *itemsValidator) Validate(index int, data interface{}) *Result { -+ tpe := reflect.TypeOf(data) -+ kind := tpe.Kind() -+ mainResult := new(Result) -+ path := fmt.Sprintf("%s.%d", i.path, index) -+ -+ for _, validator := range i.validators { -+ validator.SetPath(path) -+ if validator.Applies(i.root, kind) { -+ result := validator.Validate(data) -+ mainResult.Merge(result) -+ mainResult.Inc() -+ if result != nil && result.HasErrors() { -+ return mainResult -+ } -+ } -+ } -+ return mainResult -+} -+ -+func (i *itemsValidator) commonValidator() valueValidator { -+ return &basicCommonValidator{ -+ In: i.in, -+ Default: i.items.Default, -+ Enum: i.items.Enum, -+ } -+} -+ -+func (i *itemsValidator) sliceValidator() valueValidator { -+ return &basicSliceValidator{ -+ In: i.in, -+ Default: i.items.Default, -+ MaxItems: i.items.MaxItems, -+ MinItems: i.items.MinItems, -+ UniqueItems: i.items.UniqueItems, -+ Source: i.root, -+ Items: i.items.Items, -+ KnownFormats: i.KnownFormats, -+ } -+} -+ -+func (i *itemsValidator) numberValidator() valueValidator { -+ return &numberValidator{ -+ In: i.in, -+ Default: i.items.Default, -+ MultipleOf: i.items.MultipleOf, -+ Maximum: i.items.Maximum, -+ ExclusiveMaximum: i.items.ExclusiveMaximum, -+ Minimum: i.items.Minimum, -+ ExclusiveMinimum: i.items.ExclusiveMinimum, -+ } -+} -+ -+func (i *itemsValidator) stringValidator() valueValidator { -+ return &stringValidator{ -+ In: i.in, -+ Default: i.items.Default, -+ MaxLength: i.items.MaxLength, -+ MinLength: i.items.MinLength, -+ Pattern: i.items.Pattern, -+ AllowEmptyValue: false, -+ } -+} -+ -+func (i *itemsValidator) formatValidator() valueValidator { -+ return &formatValidator{ -+ In: i.in, -+ //Default: i.items.Default, -+ Format: i.items.Format, -+ KnownFormats: i.KnownFormats, -+ } -+} -+ -+type basicCommonValidator struct { -+ Path string -+ In string -+ Default interface{} -+ Enum []interface{} -+} -+ -+func (b *basicCommonValidator) SetPath(path string) { -+ b.Path = path -+} -+ -+func (b *basicCommonValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ switch source.(type) { -+ case *spec.Parameter, *spec.Schema, *spec.Header: -+ return true -+ } -+ return false -+} -+ -+func (b *basicCommonValidator) Validate(data interface{}) (res *Result) { -+ if len(b.Enum) > 0 { -+ for _, enumValue := range b.Enum { -+ if data != nil && reflect.DeepEqual(enumValue, data) { -+ return nil -+ } -+ } -+ return sErr(errors.EnumFail(b.Path, b.In, data, b.Enum)) -+ } -+ return nil -+} -+ -+// A HeaderValidator has very limited subset of validations to apply -+type HeaderValidator struct { -+ name string -+ header *spec.Header -+ validators []valueValidator -+ KnownFormats strfmt.Registry -+} -+ -+// NewHeaderValidator creates a new header validator object -+func NewHeaderValidator(name string, header *spec.Header, formats strfmt.Registry) *HeaderValidator { -+ p := &HeaderValidator{name: name, header: header, KnownFormats: formats} -+ p.validators = []valueValidator{ -+ &typeValidator{ -+ Type: spec.StringOrArray([]string{header.Type}), -+ Format: header.Format, -+ In: "header", -+ Path: name, -+ }, -+ p.stringValidator(), -+ p.formatValidator(), -+ p.numberValidator(), -+ p.sliceValidator(), -+ p.commonValidator(), -+ } -+ return p -+} -+ -+// Validate the value of the header against its schema -+func (p *HeaderValidator) Validate(data interface{}) *Result { -+ result := new(Result) -+ tpe := reflect.TypeOf(data) -+ kind := tpe.Kind() -+ -+ for _, validator := range p.validators { -+ if validator.Applies(p.header, kind) { -+ if err := validator.Validate(data); err != nil { -+ result.Merge(err) -+ if err.HasErrors() { -+ return result -+ } -+ } -+ } -+ } -+ return nil -+} -+ -+func (p *HeaderValidator) commonValidator() valueValidator { -+ return &basicCommonValidator{ -+ Path: p.name, -+ In: "response", -+ Default: p.header.Default, -+ Enum: p.header.Enum, -+ } -+} -+ -+func (p *HeaderValidator) sliceValidator() valueValidator { -+ return &basicSliceValidator{ -+ Path: p.name, -+ In: "response", -+ Default: p.header.Default, -+ MaxItems: p.header.MaxItems, -+ MinItems: p.header.MinItems, -+ UniqueItems: p.header.UniqueItems, -+ Items: p.header.Items, -+ Source: p.header, -+ KnownFormats: p.KnownFormats, -+ } -+} -+ -+func (p *HeaderValidator) numberValidator() valueValidator { -+ return &numberValidator{ -+ Path: p.name, -+ In: "response", -+ Default: p.header.Default, -+ MultipleOf: p.header.MultipleOf, -+ Maximum: p.header.Maximum, -+ ExclusiveMaximum: p.header.ExclusiveMaximum, -+ Minimum: p.header.Minimum, -+ ExclusiveMinimum: p.header.ExclusiveMinimum, -+ } -+} -+ -+func (p *HeaderValidator) stringValidator() valueValidator { -+ return &stringValidator{ -+ Path: p.name, -+ In: "response", -+ Default: p.header.Default, -+ Required: true, -+ MaxLength: p.header.MaxLength, -+ MinLength: p.header.MinLength, -+ Pattern: p.header.Pattern, -+ AllowEmptyValue: false, -+ } -+} -+ -+func (p *HeaderValidator) formatValidator() valueValidator { -+ return &formatValidator{ -+ Path: p.name, -+ In: "response", -+ //Default: p.header.Default, -+ Format: p.header.Format, -+ KnownFormats: p.KnownFormats, -+ } -+} -+ -+// A ParamValidator has very limited subset of validations to apply -+type ParamValidator struct { -+ param *spec.Parameter -+ validators []valueValidator -+ KnownFormats strfmt.Registry -+} -+ -+// NewParamValidator creates a new param validator object -+func NewParamValidator(param *spec.Parameter, formats strfmt.Registry) *ParamValidator { -+ p := &ParamValidator{param: param, KnownFormats: formats} -+ p.validators = []valueValidator{ -+ &typeValidator{ -+ Type: spec.StringOrArray([]string{param.Type}), -+ Format: param.Format, -+ In: param.In, -+ Path: param.Name, -+ }, -+ p.stringValidator(), -+ p.formatValidator(), -+ p.numberValidator(), -+ p.sliceValidator(), -+ p.commonValidator(), -+ } -+ return p -+} -+ -+// Validate the data against the description of the parameter -+func (p *ParamValidator) Validate(data interface{}) *Result { -+ result := new(Result) -+ tpe := reflect.TypeOf(data) -+ kind := tpe.Kind() -+ -+ // TODO: validate type -+ for _, validator := range p.validators { -+ if validator.Applies(p.param, kind) { -+ if err := validator.Validate(data); err != nil { -+ result.Merge(err) -+ if err.HasErrors() { -+ return result -+ } -+ } -+ } -+ } -+ return nil -+} -+ -+func (p *ParamValidator) commonValidator() valueValidator { -+ return &basicCommonValidator{ -+ Path: p.param.Name, -+ In: p.param.In, -+ Default: p.param.Default, -+ Enum: p.param.Enum, -+ } -+} -+ -+func (p *ParamValidator) sliceValidator() valueValidator { -+ return &basicSliceValidator{ -+ Path: p.param.Name, -+ In: p.param.In, -+ Default: p.param.Default, -+ MaxItems: p.param.MaxItems, -+ MinItems: p.param.MinItems, -+ UniqueItems: p.param.UniqueItems, -+ Items: p.param.Items, -+ Source: p.param, -+ KnownFormats: p.KnownFormats, -+ } -+} -+ -+func (p *ParamValidator) numberValidator() valueValidator { -+ return &numberValidator{ -+ Path: p.param.Name, -+ In: p.param.In, -+ Default: p.param.Default, -+ MultipleOf: p.param.MultipleOf, -+ Maximum: p.param.Maximum, -+ ExclusiveMaximum: p.param.ExclusiveMaximum, -+ Minimum: p.param.Minimum, -+ ExclusiveMinimum: p.param.ExclusiveMinimum, -+ } -+} -+ -+func (p *ParamValidator) stringValidator() valueValidator { -+ return &stringValidator{ -+ Path: p.param.Name, -+ In: p.param.In, -+ Default: p.param.Default, -+ AllowEmptyValue: p.param.AllowEmptyValue, -+ Required: p.param.Required, -+ MaxLength: p.param.MaxLength, -+ MinLength: p.param.MinLength, -+ Pattern: p.param.Pattern, -+ } -+} -+ -+func (p *ParamValidator) formatValidator() valueValidator { -+ return &formatValidator{ -+ Path: p.param.Name, -+ In: p.param.In, -+ //Default: p.param.Default, -+ Format: p.param.Format, -+ KnownFormats: p.KnownFormats, -+ } -+} -+ -+type basicSliceValidator struct { -+ Path string -+ In string -+ Default interface{} -+ MaxItems *int64 -+ MinItems *int64 -+ UniqueItems bool -+ Items *spec.Items -+ Source interface{} -+ itemsValidator *itemsValidator -+ KnownFormats strfmt.Registry -+} -+ -+func (s *basicSliceValidator) SetPath(path string) { -+ s.Path = path -+} -+ -+func (s *basicSliceValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ switch source.(type) { -+ case *spec.Parameter, *spec.Items, *spec.Header: -+ return kind == reflect.Slice -+ } -+ return false -+} -+ -+func sErr(err errors.Error) *Result { -+ return &Result{Errors: []error{err}} -+} -+ -+func (s *basicSliceValidator) Validate(data interface{}) *Result { -+ val := reflect.ValueOf(data) -+ -+ size := int64(val.Len()) -+ if s.MinItems != nil { -+ if err := MinItems(s.Path, s.In, size, *s.MinItems); err != nil { -+ return sErr(err) -+ } -+ } -+ -+ if s.MaxItems != nil { -+ if err := MaxItems(s.Path, s.In, size, *s.MaxItems); err != nil { -+ return sErr(err) -+ } -+ } -+ -+ if s.UniqueItems { -+ if err := UniqueItems(s.Path, s.In, data); err != nil { -+ return sErr(err) -+ } -+ } -+ -+ if s.itemsValidator == nil && s.Items != nil { -+ s.itemsValidator = newItemsValidator(s.Path, s.In, s.Items, s.Source, s.KnownFormats) -+ } -+ -+ if s.itemsValidator != nil { -+ for i := 0; i < int(size); i++ { -+ ele := val.Index(i) -+ if err := s.itemsValidator.Validate(i, ele.Interface()); err != nil && err.HasErrors() { -+ return err -+ } -+ } -+ } -+ return nil -+} -+ -+func (s *basicSliceValidator) hasDuplicates(value reflect.Value, size int) bool { -+ dict := make(map[interface{}]struct{}) -+ for i := 0; i < size; i++ { -+ ele := value.Index(i) -+ if _, ok := dict[ele.Interface()]; ok { -+ return true -+ } -+ dict[ele.Interface()] = struct{}{} -+ } -+ return false -+} -+ -+type numberValidator struct { -+ Path string -+ In string -+ Default interface{} -+ MultipleOf *float64 -+ Maximum *float64 -+ ExclusiveMaximum bool -+ Minimum *float64 -+ ExclusiveMinimum bool -+} -+ -+func (n *numberValidator) SetPath(path string) { -+ n.Path = path -+} -+ -+func (n *numberValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ switch source.(type) { -+ case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header: -+ isInt := kind >= reflect.Int && kind <= reflect.Uint64 -+ isFloat := kind == reflect.Float32 || kind == reflect.Float64 -+ r := isInt || isFloat -+ // fmt.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", n.Path, r, source, kind) -+ return r -+ } -+ // fmt.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", n.Path, false, source, kind) -+ return false -+} -+ -+func (n *numberValidator) convertToFloat(val interface{}) float64 { -+ v := reflect.ValueOf(val) -+ switch v.Kind() { -+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: -+ return float64(v.Int()) -+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: -+ return float64(v.Uint()) -+ case reflect.Float32, reflect.Float64: -+ return v.Float() -+ } -+ return 0 -+} -+ -+func (n *numberValidator) Validate(val interface{}) *Result { -+ data := n.convertToFloat(val) -+ -+ if n.MultipleOf != nil { -+ if err := MultipleOf(n.Path, n.In, data, *n.MultipleOf); err != nil { -+ return sErr(err) -+ } -+ } -+ if n.Maximum != nil { -+ if err := Maximum(n.Path, n.In, data, *n.Maximum, n.ExclusiveMaximum); err != nil { -+ return sErr(err) -+ } -+ } -+ if n.Minimum != nil { -+ if err := Minimum(n.Path, n.In, data, *n.Minimum, n.ExclusiveMinimum); err != nil { -+ return sErr(err) -+ } -+ } -+ result := new(Result) -+ result.Inc() -+ return result -+} -+ -+type stringValidator struct { -+ Default interface{} -+ Required bool -+ AllowEmptyValue bool -+ MaxLength *int64 -+ MinLength *int64 -+ Pattern string -+ Path string -+ In string -+} -+ -+func (s *stringValidator) SetPath(path string) { -+ s.Path = path -+} -+ -+func (s *stringValidator) Applies(source interface{}, kind reflect.Kind) bool { -+ switch source.(type) { -+ case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header: -+ r := kind == reflect.String -+ // fmt.Printf("string validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind) -+ return r -+ } -+ // fmt.Printf("string validator for %q applies %t for %T (kind: %v)\n", s.Path, false, source, kind) -+ return false -+} -+ -+func (s *stringValidator) Validate(val interface{}) *Result { -+ data := val.(string) -+ -+ if s.Required && !s.AllowEmptyValue && (s.Default == nil || s.Default == "") { -+ if err := RequiredString(s.Path, s.In, data); err != nil { -+ return sErr(err) -+ } -+ } -+ -+ if s.MaxLength != nil { -+ if err := MaxLength(s.Path, s.In, data, *s.MaxLength); err != nil { -+ return sErr(err) -+ } -+ } -+ -+ if s.MinLength != nil { -+ if err := MinLength(s.Path, s.In, data, *s.MinLength); err != nil { -+ return sErr(err) -+ } -+ } -+ -+ if s.Pattern != "" { -+ if err := Pattern(s.Path, s.In, data, s.Pattern); err != nil { -+ return sErr(err) -+ } -+ } -+ return nil -+} -Index: golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/values.go -=================================================================== ---- /dev/null -+++ golang-github-go-openapi-runtime/vendor/github.com/go-openapi/validate/values.go -@@ -0,0 +1,218 @@ -+// Copyright 2015 go-swagger maintainers -+// -+// 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 -+// -+// http://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 validate -+ -+import ( -+ "reflect" -+ "regexp" -+ "unicode/utf8" -+ -+ "github.com/go-openapi/errors" -+ "github.com/go-openapi/strfmt" -+ "github.com/go-openapi/swag" -+) -+ -+// Enum validates if the data is a member of the enum -+func Enum(path, in string, data interface{}, enum interface{}) *errors.Validation { -+ val := reflect.ValueOf(enum) -+ if val.Kind() != reflect.Slice { -+ return nil -+ } -+ -+ var values []interface{} -+ for i := 0; i < val.Len(); i++ { -+ ele := val.Index(i) -+ enumValue := ele.Interface() -+ if data != nil { -+ if reflect.DeepEqual(data, enumValue) { -+ return nil -+ } -+ actualType := reflect.TypeOf(enumValue) -+ if actualType == nil { -+ continue -+ } -+ expectedValue := reflect.ValueOf(data) -+ if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { -+ // Attempt comparison after type conversion -+ if reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), enumValue) { -+ return nil -+ } -+ } -+ } -+ values = append(values, enumValue) -+ } -+ return errors.EnumFail(path, in, data, values) -+} -+ -+// MinItems validates that there are at least n items in a slice -+func MinItems(path, in string, size, min int64) *errors.Validation { -+ if size < min { -+ return errors.TooFewItems(path, in, min) -+ } -+ return nil -+} -+ -+// MaxItems validates that there are at most n items in a slice -+func MaxItems(path, in string, size, max int64) *errors.Validation { -+ if size > max { -+ return errors.TooManyItems(path, in, max) -+ } -+ return nil -+} -+ -+// UniqueItems validates that the provided slice has unique elements -+func UniqueItems(path, in string, data interface{}) *errors.Validation { -+ val := reflect.ValueOf(data) -+ if val.Kind() != reflect.Slice { -+ return nil -+ } -+ var unique []interface{} -+ for i := 0; i < val.Len(); i++ { -+ v := val.Index(i).Interface() -+ for _, u := range unique { -+ if reflect.DeepEqual(v, u) { -+ return errors.DuplicateItems(path, in) -+ } -+ } -+ unique = append(unique, v) -+ } -+ return nil -+} -+ -+// MinLength validates a string for minimum length -+func MinLength(path, in, data string, minLength int64) *errors.Validation { -+ strLen := int64(utf8.RuneCount([]byte(data))) -+ if strLen < minLength { -+ return errors.TooShort(path, in, minLength) -+ } -+ return nil -+} -+ -+// MaxLength validates a string for maximum length -+func MaxLength(path, in, data string, maxLength int64) *errors.Validation { -+ strLen := int64(utf8.RuneCount([]byte(data))) -+ if strLen > maxLength { -+ return errors.TooLong(path, in, maxLength) -+ } -+ return nil -+} -+ -+// Required validates an interface for requiredness -+func Required(path, in string, data interface{}) *errors.Validation { -+ val := reflect.ValueOf(data) -+ if val.IsValid() { -+ if reflect.DeepEqual(reflect.Zero(val.Type()).Interface(), val.Interface()) { -+ return errors.Required(path, in) -+ } -+ return nil -+ } -+ return errors.Required(path, in) -+} -+ -+// RequiredString validates a string for requiredness -+func RequiredString(path, in, data string) *errors.Validation { -+ if data == "" { -+ return errors.Required(path, in) -+ } -+ return nil -+} -+ -+// RequiredNumber validates a number for requiredness -+func RequiredNumber(path, in string, data float64) *errors.Validation { -+ if data == 0 { -+ return errors.Required(path, in) -+ } -+ return nil -+} -+ -+// Pattern validates a string against a regular expression -+func Pattern(path, in, data, pattern string) *errors.Validation { -+ re := regexp.MustCompile(pattern) -+ if !re.MatchString(data) { -+ return errors.FailedPattern(path, in, pattern) -+ } -+ return nil -+} -+ -+// MaximumInt validates if a number is smaller than a given maximum -+func MaximumInt(path, in string, data, max int64, exclusive bool) *errors.Validation { -+ if (!exclusive && data > max) || (exclusive && data >= max) { -+ return errors.ExceedsMaximumInt(path, in, max, exclusive) -+ } -+ return nil -+} -+ -+// MaximumUint validates if a number is smaller than a given maximum -+func MaximumUint(path, in string, data, max uint64, exclusive bool) *errors.Validation { -+ if (!exclusive && data > max) || (exclusive && data >= max) { -+ return errors.ExceedsMaximumUint(path, in, max, exclusive) -+ } -+ return nil -+} -+ -+// Maximum validates if a number is smaller than a given maximum -+func Maximum(path, in string, data, max float64, exclusive bool) *errors.Validation { -+ if (!exclusive && data > max) || (exclusive && data >= max) { -+ return errors.ExceedsMaximum(path, in, max, exclusive) -+ } -+ return nil -+} -+ -+// Minimum validates if a number is smaller than a given minimum -+func Minimum(path, in string, data, min float64, exclusive bool) *errors.Validation { -+ if (!exclusive && data < min) || (exclusive && data <= min) { -+ return errors.ExceedsMinimum(path, in, min, exclusive) -+ } -+ return nil -+} -+ -+// MinimumInt validates if a number is smaller than a given minimum -+func MinimumInt(path, in string, data, min int64, exclusive bool) *errors.Validation { -+ if (!exclusive && data < min) || (exclusive && data <= min) { -+ return errors.ExceedsMinimumInt(path, in, min, exclusive) -+ } -+ return nil -+} -+ -+// MinimumUint validates if a number is smaller than a given minimum -+func MinimumUint(path, in string, data, min uint64, exclusive bool) *errors.Validation { -+ if (!exclusive && data < min) || (exclusive && data <= min) { -+ return errors.ExceedsMinimumUint(path, in, min, exclusive) -+ } -+ return nil -+} -+ -+// MultipleOf validates if the provided number is a multiple of the factor -+func MultipleOf(path, in string, data, factor float64) *errors.Validation { -+ if !swag.IsFloat64AJSONInteger(data / factor) { -+ return errors.NotMultipleOf(path, in, factor) -+ } -+ return nil -+} -+ -+// FormatOf validates if a string matches a format in the format registry -+func FormatOf(path, in, format, data string, registry strfmt.Registry) *errors.Validation { -+ if registry == nil { -+ registry = strfmt.Default -+ } -+ if ok := registry.ContainsName(format); !ok { -+ return errors.InvalidTypeName(format) -+ } -+ if ok := registry.Validates(format, data); !ok { -+ return errors.InvalidType(path, in, format, data) -+ } -+ -+ return nil -+} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.drone.sec golang-github-go-openapi-runtime-0.15.0/.drone.sec --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.drone.sec 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/.drone.sec 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.LWdLoZr7D0nRXsFIC4Wfr_xX-1Evh5MQIakeOV4aXVqvMGMYWjAbLyXaWT0Wmwkl8ZGXzyVJGxCEJhAPiCXG-XKdxZrLjqpPc4eWuajF0CFoCoBeDNkGINmx8_4Vjq2D6C0bE2UN2lBqpUpexobdFWNVFcl7K3v-rGELr3VKa29Yp4dGkf0kjgOiaf7x6dJDXOdSo4ioNN08sfHaH1W7TYe9dl64GrSduKj_IKnLB6aA3Ckk6zOHf9AMQVpPAq-fKAMWQJgiiUGzpOPewAxmqdNZfMAt0FX_J5Jy8Rl76-rj2J8qMzKkHuWDVooS3kFHk4pEy5CVNQ2ee6PnXrhkuA.xuTULIjAQ3BCYx-9.lQ8tTXWaltPSileqAWwAPsm0ThSbIQB7bAKPN86dcm0Yq2SoStqFOH96Ut6_kGwk5S_0iO81fEybma-Xh508dTcy1IkQUaSUZ8At-GHJ69XH_r46V4hb6DbiMKBHu8pSOKuKehrEA2vr9CgHU7R_7opRrvwUJ0cQnpG_UQGSzckAowxFvgqv05ckFncTSNqRI-no7rIaPpq3ijCmvHaEfGFsmdB0i1Sy7eDMECn50qs9g88jLXQogcnZ7Dt5UUlvb9zXBt1rzAoFJiA6lrhTi9gv3CH2x1MA8X_6O4t4ob114vZmOH3R1YG4F94Yo7thd9jNivl6vu54pJRyIX1zPILHW_-KHqH6RRyywCuTU-ka0alLOlz4f9RCNBZDEZC0rk8Nf9BP8GfOO8EYMS4LgyiFv45HQzTLEvXTkkwdNKSVJfalZmMTEfFlxYcO3F9HvPpdTQjf6zB0BI9iUgMYow9seoSwPZhgo5HFffcCPME5L7pvvuSoEf7uJtDbYp46QhWIT6gTQ_GGL4z_K187Hgrqv8zddYtTqJuPQCvvddccPGLzxlA1RyUZdyrwvDII_Gwn2kChg0EM_uLMTbSee16eUaSAtx0eRnmJ0i6oPVJNIKZZHsZ7YMMwufNS6ec4fvE.VwKOnyCMKTr_b8EQdCe7Ng \ No newline at end of file diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.drone.yml golang-github-go-openapi-runtime-0.15.0/.drone.yml --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.drone.yml 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/.drone.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -clone: - path: github.com/go-openapi/runtime - -matrix: - GO_VERSION: - - "1.6" - -build: - integration: - image: golang:$$GO_VERSION - pull: true - commands: - - go get -u github.com/axw/gocov/gocov - - go get -u gopkg.in/matm/v1/gocov-html - - go get -u github.com/cee-dub/go-junit-report - - go get -u github.com/stretchr/testify/assert - - go get -u golang.org/x/net/context - - go get -u gopkg.in/yaml.v2 - - go get -u github.com/gorilla/context - - go get -u github.com/go-openapi/analysis - - go get -u github.com/go-openapi/errors - - go get -u github.com/go-openapi/loads - - go get -u github.com/go-openapi/strfmt - - mkdir -p /drone/src/github.com/go-openapi/validate - - git clone https://github.com/go-openapi/validate /drone/src/github.com/go-openapi/validate - - ./hack/build-drone.sh - -notify: - slack: - channel: bots - webhook_url: $$SLACK_URL - username: drone - -publish: - coverage: - server: https://coverage.vmware.run - token: $$GITHUB_TOKEN - # threshold: 70 - # must_increase: true - when: - matrix: - GO_VERSION: "1.6" diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.editorconfig golang-github-go-openapi-runtime-0.15.0/.editorconfig --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.editorconfig 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/.editorconfig 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,26 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Set default charset +[*.{js,py,go,scala,rb,java,html,css,less,sass,md}] +charset = utf-8 + +# Tab indentation (no size specified) +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/file.go golang-github-go-openapi-runtime-0.15.0/file.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/file.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/file.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,33 @@ +// Copyright 2015 go-swagger maintainers +// +// 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 +// +// http://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 runtime + +import "mime/multipart" + +// File represents an uploaded file. +type File struct { + Data multipart.File + Header *multipart.FileHeader +} + +// Read bytes from the file +func (f *File) Read(p []byte) (n int, err error) { + return f.Data.Read(p) +} + +// Close the file +func (f *File) Close() error { + return f.Data.Close() +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/file_test.go golang-github-go-openapi-runtime-0.15.0/file_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/file_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/file_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,20 @@ +package runtime + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFileImplementsIOReader(t *testing.T) { + var file interface{} = &File{} + expected := "that File implements io.Reader" + assert.Implements(t, new(io.Reader), file, expected) +} + +func TestFileImplementsIOReadCloser(t *testing.T) { + var file interface{} = &File{} + expected := "that File implements io.ReadCloser" + assert.Implements(t, new(io.ReadCloser), file, expected) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myCA.crt golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myCA.crt --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myCA.crt 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myCA.crt 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE/TCCAuWgAwIBAgIJAJ0kpLFo4pEzMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV +BAMMCkdvIFN3YWdnZXIwHhcNMTYxMjIxMDgzMzM4WhcNMTgxMjIxMDgzMzM4WjAV +MRMwEQYDVQQDDApHbyBTd2FnZ2VyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAzQ5NC1JBNNP79HPiUBAO59LoUMGbmSU9K9v+cQMuyyOuv0nwuiXc5anU +J1BINqgLR1VJjwTnQsXSlsr2SPs/144KgTsgk/QpMXdlFQwfqLJBIFlsQQBbMx6L +/2Ho6KE7z/qz6cqgKvYrGDu6ELUu016MbUsPWfhPBJE7Ftoajk5AIomDPmiTi0cZ +wdhC8SB0aVVQ2IWrsusfgPeOQ+ZLa/WHmpJ2Syfq41i/VKllEeCrMwtMP2By2kA/ +ufBLCnhr7yZ0u22O1Bl1+0XedWli2GiXyt1h9nQ5blTTKZi5grOzAgCcshb/bw1H +1hdJKMqkzbqt2Mxc/78PJbDgicJU1ap+fhfBmUviWIMML6eum2ObuKd4ihhXKfqp +T/nSUA0P9565W71SLAHFLdZX/VSMZnoehkwIicVGgEzjlYj2j9qBc0CjYzbEtQXH +TRGhbjMX5LSByeE6hwLM6hIbQL4nriRobar63rbOc74Tm1ed02R6BvQjgXgOGqAN +BgCKKjfUIm0Qm2qV4WkwGIAOi+hdUpbNJ0X2dU/B00qLhar+h4NT9TW4PmKf4agk +NZ6O3C1saGxjtuPnIdDxWTdRhPSUyjsllmWhrmkY2bsRB8Z47zqrdfyajXlPOmBM +1f0am4Zeo3ditBTfFqtA2LLQbn1yZwYJQ8+sESu6bsm3S89DFT0CAwEAAaNQME4w +HQYDVR0OBBYEFN4BShcjqDbbgaGvPiGMNrUEi/RZMB8GA1UdIwQYMBaAFN4BShcj +qDbbgaGvPiGMNrUEi/RZMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB +AIqZYn+PFMcOwraYtXtAzJwU4gElWDjIA+xVMMzulHde8t7EWo+I5iDdtn+sHzhG +B1oYFhKv/HIX9GR3XDYWluoUVA4Ed9eBamL0Qqzq4Era6ZN1VQd0egvyOT40UQ7m +2aNw1nLgWadCmsxeVMKQRdzct9G3dOfJp7K5WybINWTibNMTuoSuU5RwtzriK000 +C9pnxCD8boSNY/flOX0M5Mt3kv2JaIH2UsMKNGBf5+rXcKfhTE6JgiXorUEEztHP +PFpZ6VFKDlr8QC/4aLYhOJ9LIloaxZyk/rccCuHbdPPX5XGA3Z9i/lxSoqtShYlS +mt5vmdRwQob/ul6hPch3YRqD4VgeM1O80FEsWBK2WmGGH3wKNKea7u6dZyfQv3t3 +fUVmByAVMllVRA1YiKmBZ/kOeAMku5hpR9kzErCXZd/xrKWVym000RsvRb6apltM +sYnlCyKfIdKxUXavO0Bf4+YoaN4/p3mZchxpLBwrzhPyUpGQ9b3TuGjoEmtG57yn +6I3U40/TouJR0aF7i1bAF5QJWYOS7OycJbHAIZiQx9ENDP3ZMfYNWQO6STFJAjvC +C0u23DyiJWZqE4Uw51O7jWKh7bSEKWutwa0XKWrpxhUjHFX4qGigIvXpO9LMjR60 +YDhdCEmUiu/Hc0tt0QzyTA6w47TP0gXREeBLabzuEDPi +-----END CERTIFICATE----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myCA.key golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myCA.key --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myCA.key 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myCA.key 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKgIBAAKCAgEAzQ5NC1JBNNP79HPiUBAO59LoUMGbmSU9K9v+cQMuyyOuv0nw +uiXc5anUJ1BINqgLR1VJjwTnQsXSlsr2SPs/144KgTsgk/QpMXdlFQwfqLJBIFls +QQBbMx6L/2Ho6KE7z/qz6cqgKvYrGDu6ELUu016MbUsPWfhPBJE7Ftoajk5AIomD +PmiTi0cZwdhC8SB0aVVQ2IWrsusfgPeOQ+ZLa/WHmpJ2Syfq41i/VKllEeCrMwtM +P2By2kA/ufBLCnhr7yZ0u22O1Bl1+0XedWli2GiXyt1h9nQ5blTTKZi5grOzAgCc +shb/bw1H1hdJKMqkzbqt2Mxc/78PJbDgicJU1ap+fhfBmUviWIMML6eum2ObuKd4 +ihhXKfqpT/nSUA0P9565W71SLAHFLdZX/VSMZnoehkwIicVGgEzjlYj2j9qBc0Cj +YzbEtQXHTRGhbjMX5LSByeE6hwLM6hIbQL4nriRobar63rbOc74Tm1ed02R6BvQj +gXgOGqANBgCKKjfUIm0Qm2qV4WkwGIAOi+hdUpbNJ0X2dU/B00qLhar+h4NT9TW4 +PmKf4agkNZ6O3C1saGxjtuPnIdDxWTdRhPSUyjsllmWhrmkY2bsRB8Z47zqrdfya +jXlPOmBM1f0am4Zeo3ditBTfFqtA2LLQbn1yZwYJQ8+sESu6bsm3S89DFT0CAwEA +AQKCAgAjBkBOoLwWg+bTOD/9oOCK5FFeCdPD8sJiDW+Gah7B/9RHRB/kC7eRWtKr +7GCJRWa3xm+MCDBgDV4M95ndmVysOsy8ihbkbp3inxwa3jlCHUBWgC+nYqIxNxR+ +iIC5y2BmA9JbKor1C5sMxpbfZ7MZ01p1CI8UtP76LrxDCPnkOKVnwMk0DbS1420Y +2RGGEh8QJsxqT1qmctastpwMKPfU9tk0o7Ok3qqWLoBvu4dR6GgVjeZ2JMk5UiQQ +ZGTM4wi8jnr90JbGz5qBUsvOjjOd9y+GLQ4ghHWSzNZMkpONKZh3zRb2rErw8vnE +LbIHT6Wapjovf6ia3k1+CJoxrYnDrsOHcWopm2kle7FXjgfHRXubcNU2aLdIAcRg +ZGGyalex3/NXKjhGf8jhaXKkOYDL37ZFtEmaUJVjjhiIE5jGByBHU0pqKk9Tdtv0 +s5r5m0T8Gk8h70+fZ/C+wkYE4h8uzqAlq/yrxBSlGMHEVG9PI9tr9bM1FLM/H92q +CqoVR6YWTC7o5Kasr33RKYJg5vPHfFoIGHX9etbfHPGQsbCLaWhTLIYus+0b4ZS1 +D1jHCoxHCjKzf2PFwogtRsmhyQSS3A3GyEWy7BZgFvgKFpq9hRC66k8Z7pnnkKrW +i4YihK17ivI5uG67Aqlc+kdahRNVWOOaPbwjGosmlULyfCOdGQKCAQEA79dD3caa +zXqFQkYDZwZcthXw9Hn+ChTCcfucJz9I0DUeL8FpUQYCkFZRUoEgF/eNrKSG+ctn +VDgrj0IZAcumpfjc4ZMug8svFYkE+vsUVgSI03KPRJ5NZ+Egn+HExURzCSQY6fK8 +mCp05+gXndiUhoco2H851esmMtCSd/5IyR3d3C64ZfFGSk/Nx66A4Z643ffB6tOH +KYWFgVoQtSb92pgyxuBzZ1JhxuBVihRzAQtuE+uZ14xPoVv52fUlYXUhGmdqtZ3l +Cio3YGZTaUqtF0BP8HshzAWQ2k2vCJUxY99dbFfsE+v8vCojgMz8KmzO7C+j3Pa1 +hq77rT29WFvaHwKCAQEA2t8R3QCkcaCRDMAPomqPJjUHrX2gdPM2zFFCvSqiVkh6 +8ft9NF8sO1aXq600IxTiTf/L8ZvM0HlPlYQSjFzGWsOgNww9MKF7L4NnJ7e/8mwP +jqfajNcqecHIXvNi0AqXOpN/hEhm5MWKce/BPV6GpnRnb5doy8wOG0ESsmUA/5TJ +y/65LVxDKT9SdymDVayRwq2vNn9qW2BBcM9yan5GstkE3zzkrzKcCgz5X09/vO3R +K3fYk0FReE9CY9XAQGtz36Ra19efETzvWPi18zsP96QMUYIS2+Y45sVPhGZbY2aG +HQXTg8xIJN51E+jmWpJ1vv27izFh5TXeloRD4qldIwKCAQEAqkG6+KVy4OjXzlsb +MTiP+eaLfVFYaFmiSv3dNPM0wjDi8+2t0Imeqk3MPvBRExJ17ReChbLB8ERLj8/R +Jrgl3e5TBoLP41kKXJQ/B9fS8NkZNFk/oOtrcZGb8kN3xr23l8abNQBOpwqEoNfe +Y/wKO5GZCk8OhHAAVtQ/FZVaoAJmq1YzKpLjXf9WyihzbzaYb2Hgs81jRrN1OYTx +FVfPnyyp5woQgkk2BdLchj/L//LYOqXmOOBu6tH7BKGE3rEiRbciRjkHDXc4hmM9 +VSJgy3+o/8K5FDbjREUfOs2GGSrIDBBCE0ZTzFNxjo51d7x0C7Ap98Ley/RNzwZj +8mSJ6wKCAQEA0NXvcXPfdBvEyumnAU2zcL1AqiUoKO635pPSnjRD2RgnVySi/omg +5q1k4oXNLXwLwmjD67DA6FoXuY3fNNaA3LGz+VJQQEqUA23Zy2fkWicJYRB/08qp +2KsxyIdqTR8N1PJPxaRfqQFja/tb4naC++gtmahacbot64tXj6gYH8WUFnThs4pI ++t5UjSarDeAu5BZdDB7fGHjrd/w4K6x5QMUZhPfRK+maQWzHtE1ikJ5J6rPbjgXQ ++n6F1kRpwA3G7ikgFLrEJ+qAZeBJm99LCPsaVdtKq08sE+VITghsQpfcd2zLuQH+ +BE/OXkTnJpyAhNANVm6z/cQ8sllZfLglCQKCAQEAkZTQ0xnUeJTV0CqljMNCIN4M +i6Xyqf5uUDPfooY+fILbemT/acYUAkbjamgJlQMQ7yui9WV7Q/9HiCHaVh3o5zrV +zaq3vocA9x9nK998/He7772/uJarINvLFj/p/vTQM4Lni+bJ93bk6XE+FQKPgY9B +GfeFFaVtH7gimB4CjrxYprhAfqyxyE/m6JVMRg1olIFuav37GYP+TJ2K85klQRNa +TEXbm6ZJpSHfNjKZzUczziaIbwnMN9OxJY6M3a1JuEy2h+og5oRdMOoB6RETzhle +mxT5uEtA6mR6KyBZBjWhcl/V/Rw1DVMmtVbHCdc0+Xn/CMemRLCw1bxRUu/iww== +-----END RSA PRIVATE KEY----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/mycert1.crt golang-github-go-openapi-runtime-0.15.0/fixtures/certs/mycert1.crt --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/mycert1.crt 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/mycert1.crt 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCApMCCQChJZEdSdrQkjANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApH +byBTd2FnZ2VyMB4XDTE2MTIyMTA4MzMzOFoXDTE3MTIyMTA4MzMzOFowGjEYMBYG +A1UEAwwPZ29zd2FnZ2VyLmxvY2FsMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAs1MHhleossLBkYKYwOT+82RT651CfCOilpEUhm92KGRSMQXZEk+2TUgc +dGPeQNDNmbpXGzdk1HZkqWR5XKfSjWWxfmBlbBoYnkL3neoiXBdBVsgHkEPdP5ly +uJRkohy6az1vnq2vLaI+YujStutf8hSdcPu9VeALbrR027dMbY2XMC97FteeVaw1 +mXmW9UHDVSV9UPBPswUOQWhjIADBk5IYaYASCY3M4X5BPCWFu1oQhgVMEhodBoBj +pHhHrfoDm1TwtT+dp53TmR10zpUiN+FcaVMsjqN4DWX4ma0uhu+zJew2XjCJkNfX +wVqGFpe2Hx+lupOGs/kwBvQ4PYn5ydgcm5DTggBC45JxCAVi3tQCYGsg2xkX9yPj +aXYc8E4/aeQI9UZxUeR2siBn8ECX4gJTmPJbQ4Xykqn6YOHyxIVoqd+9wo9Z1weH +xCWtPGESg7l7Jn/6WQ5V8z6RzrquGi67asrpYpv2lxNXMQA0f3S8sWYe4f8QVazy +ALtu8+0XE17UPjlbNBqEfCIrMsYmL5VyMVbL0dlXXBxHjzfpXraNGoSD4v6LxRxP +dWQgrhEZ6DmfiWfX8uhLdMwlvUxNXj33UDtM8dtN6mHERA9wF2RQQzPddZ0MYmUF +DI92i9mRC7Yzx6mcv/yUnFw213Jnzg297lW0Xp0ifawyPi2V8f8CAwEAATANBgkq +hkiG9w0BAQsFAAOCAgEAme1gyNQry3E5bj4XfdL4aNvZamzLaQVRlNZSHUzDhhpH +6N/DK/CAw4g4Msty7g3KBZPmldJhxH0bnSoRGMjFdKn9tVQeJOjaHQ2Z3cQWwdte +iXtu2F38SVfP5HCh9ASQ9vQXahGOruUPUUNUnDLfOBea7vrT3DmVugXlMSmaYuSJ +JdrbPzD48yy60AEDlCVpY2m1cEc5SmTkXbrAg2jhQd6ytaPQ28vGQnpZHSS/xWjC +Hh68o5SUoGoFErZxPd0o2brHavi4YybYt7CXlWG2TJ89s3BCSPIHclNF2HjxRq/r +2Q/Ttzo3cRBxi3RBnrLdn4qNgJjZnWaLobjaWcs1fbI32allogLsiurCwZb0ToC0 +fNMzyHVNWY8BqsuyWyF2H0F9rklmqGFJSmrqt8kDLx0xpkZchGPIDSRh+f+PPDmE +jGPPH2qxz4un0foJx99dtw18TPaplFo2LxRK89koTiQNyzAHwSn6PHGlyXhNPsUt +K5GzjAu6B4uyldcg2m+4O/dbNdeqSczYAFenfEO7PRAy3AP7Lxs2xqQaNiA10965 +vYmCNIOuV24CuFEIrjOQkZeFCw+odsgFs5Nv8JfDdA+BRr+Haq8FVX8afEc0BEnr +xY6f2fvgYTMvx0Z3UVT/XJ3POWHRL0HFLj5avHE0eOOkrcPbX6UsANd1v0F2BH8= +-----END CERTIFICATE----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/mycert1.key golang-github-go-openapi-runtime-0.15.0/fixtures/certs/mycert1.key --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/mycert1.key 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/mycert1.key 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKQIBAAKCAgEAs1MHhleossLBkYKYwOT+82RT651CfCOilpEUhm92KGRSMQXZ +Ek+2TUgcdGPeQNDNmbpXGzdk1HZkqWR5XKfSjWWxfmBlbBoYnkL3neoiXBdBVsgH +kEPdP5lyuJRkohy6az1vnq2vLaI+YujStutf8hSdcPu9VeALbrR027dMbY2XMC97 +FteeVaw1mXmW9UHDVSV9UPBPswUOQWhjIADBk5IYaYASCY3M4X5BPCWFu1oQhgVM +EhodBoBjpHhHrfoDm1TwtT+dp53TmR10zpUiN+FcaVMsjqN4DWX4ma0uhu+zJew2 +XjCJkNfXwVqGFpe2Hx+lupOGs/kwBvQ4PYn5ydgcm5DTggBC45JxCAVi3tQCYGsg +2xkX9yPjaXYc8E4/aeQI9UZxUeR2siBn8ECX4gJTmPJbQ4Xykqn6YOHyxIVoqd+9 +wo9Z1weHxCWtPGESg7l7Jn/6WQ5V8z6RzrquGi67asrpYpv2lxNXMQA0f3S8sWYe +4f8QVazyALtu8+0XE17UPjlbNBqEfCIrMsYmL5VyMVbL0dlXXBxHjzfpXraNGoSD +4v6LxRxPdWQgrhEZ6DmfiWfX8uhLdMwlvUxNXj33UDtM8dtN6mHERA9wF2RQQzPd +dZ0MYmUFDI92i9mRC7Yzx6mcv/yUnFw213Jnzg297lW0Xp0ifawyPi2V8f8CAwEA +AQKCAgBZtF8/RPqO8f4C3GGtnOAswTN52eE4WFstFsY9ueRRsF2tSE+eaWG4pyoU +zyCPK+St0hlg9ATsg403b5uGTi11rjlsDqyttyA5iyZzSHyHtNpqnwWplUlIV2qc +Cx+MOPLIUqNTrW7EVTUAJZfDCVulrcpUipncK4eMiZkrkDYbV4kaAaaBdrsuAEeP +ztNFPPCJ14coxg4Yb58B+UYc7EPpnlu36uka/mRPKOlZPSv43MUHRf8XzxhV+EPg +Moso7LiBK6x9/qTPBJSlM6cK8G99pK6lwYW4lO2pRilmNsvflGj5v4Ay/fTTECZO +AwqwopPoXdx5yPLJdQ4hbGn13t+k0pB4LYXl1xqLg2Z9QN+pgC2h41OrSx8Ozw9U +KTocbsMV6pafnMRoQ5Fjb+eTy4VE8rZl/OlMDX2cR2XL+a3ypIAA5E4KrYDiIBiU +MSA3EA3GsOOnyrV+fII+f2tVo/qDnvxQO/ZPUr/XG2xtJ+gqThWlrBft/O4/lCju ++kfNg8cMHtahGOmLz1ALsl32ANj5jTZmVOEs9xTG7+TeQ2RzWeBYTB7oNTMNIbaL +pTZTzxoeRyxx8sUvtaTb23IWSpRUiS4+F7Tn97g6ks8fYQPsVkl3WzXeECaL9uNN +hFkAwd0omD4TwQlmOUVm3IH7A0InTAaooC9jJfNqmhhHcLUAgQKCAQEA3N+pR1ed +aCXQit6bgw0rIF6RzjeGp6lLGaPdvCUM7sdAUwSGbFOgkcaw9TELFpCpfZGKFXI9 +IxPOwjFrURY4S2iuyAVv+Cw7trXW4BF1z+08M9RWYGLvyUsO7FIsGUmdYRtasb5L +IfHfGoXttadKWcdFMSF+25CUcbleyCNrJzXOzeMn1/UoN6+qfsyfaAD03nw/ZmhA +mK3UKjR7UOUPXt9gIXVivRaEQBakrLkJtK33fl1hztqxComE3/h6Qmj6iRmyxX3y +v3mzXbyC6jobq1tLUWpxvlQgoAyk+zZ0LNEHlkVfertaz0XdN/L2ZgjoGjJxfn/y +OK0a4jJyCpXXEwKCAQEAz9fJcpqaB25joZsqG+islNljyFjJRT9+E8IU97OglhJM +8T9TFs4RNsAxIqHwYm4KuoSlI4GhYeuOxGk6yUGm8cO314J7Wv4CAgZeJBcmxJUs +C8FbcXKbesK3VIuvATgzI9Vq/UF+GxJCkPctZJ9Oa0t578rVS4JH5ZJQdw2A77Lq +kGunMDboVY7EYDOn/fNMKGfcnH8KIQn/2b6CKLarj39b1fG7MeCuxPRejijaKtZI +ra5n0ofLluGo9HBXLgqgsjhjkSWU79lRypuKS8qOcjI08Xaw3Q8+qn0uLCARd8rN +2+mQy5OAZJCWpYIKPL6E41GaxcaTdOYzlOCs9Oz65QKCAQEAgSqzXitYvC1RFcU1 +AKDU1as4bXZ/YtFYP/halcq9E26mqWX+Dp+hSV7+4YT6zQlwdSSFsiEKq9bLlTk9 +X0A1T7Q6cnLrliCYEzOoI4VSdnRwPocwtFFnlTo10fIEJA2u4bkTgtqcKY+/P02P +RCo/Ct3EEwVZoKGejhsv2K8N3PJUrIbpKBwQlvA+LsUPe80DZpEWqpbRH/iYGM50 +R0yNfpf3KdnyEk52rNwRFYloqacLE3Uc29F8s4LUl/5B0VB/I2pJ58DOEzfiszCp +Br1QrRdIpqYvOnUMV0zNtrOToRnk6/ZJ7gZfBtP+mNeXTPhsc9WIFchRKN/i1uFV +W+dgzQKCAQEArcXTDdio85GeB1395PuyX3kqbjWdgiJFvStF8JvkpdSDNCknxSdh +SQ+DhVsz6nfqzGtezsLxNTeHVDxPBDm55OUobi0QCdHZx+ufBjm9FhtKikGNvNp/ +mDH4qd1n4nMkfs9O9pOtZeDsetvOvhRbsmWWe6BwmQNCLXUZhZBqvv4uE7WOQUeH +FRGaqnxF9pNWl2nPD6E/zMPZgCpCFNw1sHJhTA0h39/k/5L5A46waaRje6MX9vPG +ik39vvG2Ui5ckOWIibCMR8TBF87X3+ppEp1bmo8L7Kd0U4L5+baOJEQRvc4YW7zl +Wi9xZMvG12bLIGv4JWeTnediNRVsRhNk6QKCAQBXYkpxk6LTgP+b6FJ7fiImzDbH +QJ+RbBYJdgYLEarKWdWhNqj3YiDOUJt+ve13reybL4cLmOYoNzeUO9IHyGeTp+WV +gtPf1g2hm2TZannWsoTvnoXJ8yR52ZQQ5JusNosbmlrqWRAN8GhISdYTJDNcS2hD +PnVX/kaJfRDennokD+FWuyygua66LBdZi3UNgGMay15/2CCoC3PoejfQORxDyPP9 +am+e3/U6QG1/VWMHen3Mb0AZKwEBAwX1jL4EpoDZ+Y6jP0tbQ5xL7RivsUNtAVlQ +m7lumflcBy1WqkmviVJ9M2iFuo0HznuH1qlgOJpUiqZZjL/gEvkdDNMcQSmH +-----END RSA PRIVATE KEY----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/mycert1.req golang-github-go-openapi-runtime-0.15.0/fixtures/certs/mycert1.req --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/mycert1.req 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/mycert1.req 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEXzCCAkcCAQAwGjEYMBYGA1UEAwwPZ29zd2FnZ2VyLmxvY2FsMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAs1MHhleossLBkYKYwOT+82RT651CfCOi +lpEUhm92KGRSMQXZEk+2TUgcdGPeQNDNmbpXGzdk1HZkqWR5XKfSjWWxfmBlbBoY +nkL3neoiXBdBVsgHkEPdP5lyuJRkohy6az1vnq2vLaI+YujStutf8hSdcPu9VeAL +brR027dMbY2XMC97FteeVaw1mXmW9UHDVSV9UPBPswUOQWhjIADBk5IYaYASCY3M +4X5BPCWFu1oQhgVMEhodBoBjpHhHrfoDm1TwtT+dp53TmR10zpUiN+FcaVMsjqN4 +DWX4ma0uhu+zJew2XjCJkNfXwVqGFpe2Hx+lupOGs/kwBvQ4PYn5ydgcm5DTggBC +45JxCAVi3tQCYGsg2xkX9yPjaXYc8E4/aeQI9UZxUeR2siBn8ECX4gJTmPJbQ4Xy +kqn6YOHyxIVoqd+9wo9Z1weHxCWtPGESg7l7Jn/6WQ5V8z6RzrquGi67asrpYpv2 +lxNXMQA0f3S8sWYe4f8QVazyALtu8+0XE17UPjlbNBqEfCIrMsYmL5VyMVbL0dlX +XBxHjzfpXraNGoSD4v6LxRxPdWQgrhEZ6DmfiWfX8uhLdMwlvUxNXj33UDtM8dtN +6mHERA9wF2RQQzPddZ0MYmUFDI92i9mRC7Yzx6mcv/yUnFw213Jnzg297lW0Xp0i +fawyPi2V8f8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4ICAQB7U21HoBp5Qfrwd8aA +KzBY2BfEp5ouyn32kpfkB3Ha6+XJ69Lt1WHMSKnmYamlwZCSOS2uQ6DzdTLDfZpC +8PH5Gs32O9zJwUeSuYcUQGfcAenauu9gwC5ZnIbhOs5YTnEFquVsBqrNUKS+hLKJ +sAPtucoqlLX5qSkv/BOK2X4os90LAmx+yB/yarAzZOO0ku8qXt+MHI+rOMPLTmm9 +kYhtyXejQaXLOVbvQ9b2gxHvMcyLhklc4KpJPRfPzOdNebHsf5o4Em6lxeglGw/A +z05sBSAla69sEygcItZryQ4WjMRUpsLePXJrlSL5DYWGK6BX1gCkWtpXLqE1HgR3 +4L/xvaJQ5ZWpLoyJoJauU37Zhd5dLNGpNiSSEA0BKOjj9Kjm8nvsJE9DgziTaG57 +qFLRkMkDdBdb5wOfVYI/MY9zc+igrFPQJkQ0Xkdza8yXegBldv1JRe+49zifysea +Y/B+qWx8IpeHke0iEMqR6iWrw6oGBG/obHJ/V09DwC6iU8vot+pLr/bSyoUCUP30 +OEATJf50ic9oZYXgdT9oNBcAlAriuzoQuGi9nAKZJss6YkhooWoqXlXNQgAEc2gl +WF4fNumXwVaPVeW2q36Xk1btHz7k+IeVUg1jaPMPUJ+1dgIOZA7FcoYotvF6StyX +xoHybhvC7lbeif8EK7tJ2p4hug== +-----END CERTIFICATE REQUEST----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient.crt golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient.crt --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient.crt 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient.crt 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE7jCCAtYCCQChJZEdSdrQkzANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApH +byBTd2FnZ2VyMB4XDTE2MTIyMTA4MzM0N1oXDTE3MTIyMTA4MzM0N1owXTELMAkG +A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAcMCE1pbGxicmFl +MRIwEAYDVQQKDAlMb2NhbCBEZXYxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBALGjhpJfej7btWCO4OCRJBliUAUyPMO8 +B649Qjn1Yiw9E1L5viByYJSihsfUQ7u2gHip7QdigKCA/s4+w8V2L0Dv8lCowCLk +exf10b7XGQaOqhk2mlr/jOapAg0pKDoUlVErBBZK2s6UbD/gLXAbxudwxCFKJ1Y7 +d/Dw5aTl1vlWZpHzf2o9/ZCeHXf8Xu3aMIEPJ79wG0vzNZK7bL1r1lQVzACdHAr3 +4HAQAvgWB4ZjKqN8z0vGC0N0MpaAuHD8fH8wQ5YiWBbDhDPFVzRYU8PcQjeZSMFq +Oulew9KVm+vXtcMvteEoXMXwWlqAGlvnv7sskc/VbrLJJQaoswyKgy1QCKxVO47E +f2iU4kP75iDYx6NpApdnpN3zxHMHyZDxuwmtoKealenxl5cZeHc6uUU1wXk+nmy7 +TrgW509mcopHzHj+Q0zyGUg/dRws3qXPAGZehJPoaYF1F54eiindF1yLMMH5osvy +1bNp2EQezOlY3P4gqW9VHq3CQvytmDbXqS0vPzVAsFu8YazM3Bs0mW2bBXrEsajW +DSjrvbhdZjlL9j2jqwZ2nzyan88M5t5T0vZhcu+wKisATI1yLdV3oWvLmdFz/XA9 +L6UyosTiwC1MWPmkOY4mcHn/Px70f40+wO815pZ6FbjecxRSyMfAm6vDPWtLAMUr +1UoD4vasyvQNAgMBAAEwDQYJKoZIhvcNAQELBQADggIBACI85R1LfDwKpmWFdCeN +M8O6TwSftxh113QzMvobYDMmBI+yVn0ZvpcfP7E+LWRjzNcDxMFbntbbFWt99Rcd +rJek6CVxLJug53mUEFmvktreHsJ1T7cMbk1ZVroAurE7hZOWYM2HwXlKzVyXX0qh +wR26HSuzQcGBfo5/8e6dzMuy0fUVkgDMu4oKt0+mGgS4kXsOyexfRRBkY9GPusVk +gSzu/WbSGNxNvp/ewWNi8waqrN3noM83iae+BXxI0Sq4eLTQ/vnV1ReM4gRR12Vw +anwZqHZ/WzBV27z9gW36t7wRxJS/uTXQ8J08KtBRBPv+19NXSqqjys5Jg0P1f+l9 +k+sWwpVqIF2rAQ3FyMfboaFKPC0jRn7iJMjp9KyvMbSI+25/rP5xvMicoJwRlk9I +GNGasxSfmRpVpV+WG04xMGp3cPrCXHBdAAjI3O68YIPOX3VqZ6MasN1iGuYWOmam +yeKzLUApYdtkR7yJ+X1FOKVfbzX27CLYmzwrHnDLJzu8NVgqLGU+qTSK0zm3sYE3 +w3ex6WX86Oz2QBJ5h/s2TLbsWis7ZkKjMyXqVWlbg4P3reyNrfpAoc0y1R9EjZlf +1c9HZBRBuRMgaPWmdSR4lxw1FhQBTstIfzC8lBYNbt8QRRtJIxVF9mxiL7H+6XH5 +FZXcQCHun6klGtCkypeAaviE +-----END CERTIFICATE----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient.csr golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient.csr --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient.csr 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient.csr 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEojCCAooCAQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +ETAPBgNVBAcMCE1pbGxicmFlMRIwEAYDVQQKDAlMb2NhbCBEZXYxEjAQBgNVBAMM +CWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALGjhpJf +ej7btWCO4OCRJBliUAUyPMO8B649Qjn1Yiw9E1L5viByYJSihsfUQ7u2gHip7Qdi +gKCA/s4+w8V2L0Dv8lCowCLkexf10b7XGQaOqhk2mlr/jOapAg0pKDoUlVErBBZK +2s6UbD/gLXAbxudwxCFKJ1Y7d/Dw5aTl1vlWZpHzf2o9/ZCeHXf8Xu3aMIEPJ79w +G0vzNZK7bL1r1lQVzACdHAr34HAQAvgWB4ZjKqN8z0vGC0N0MpaAuHD8fH8wQ5Yi +WBbDhDPFVzRYU8PcQjeZSMFqOulew9KVm+vXtcMvteEoXMXwWlqAGlvnv7sskc/V +brLJJQaoswyKgy1QCKxVO47Ef2iU4kP75iDYx6NpApdnpN3zxHMHyZDxuwmtoKea +lenxl5cZeHc6uUU1wXk+nmy7TrgW509mcopHzHj+Q0zyGUg/dRws3qXPAGZehJPo +aYF1F54eiindF1yLMMH5osvy1bNp2EQezOlY3P4gqW9VHq3CQvytmDbXqS0vPzVA +sFu8YazM3Bs0mW2bBXrEsajWDSjrvbhdZjlL9j2jqwZ2nzyan88M5t5T0vZhcu+w +KisATI1yLdV3oWvLmdFz/XA9L6UyosTiwC1MWPmkOY4mcHn/Px70f40+wO815pZ6 +FbjecxRSyMfAm6vDPWtLAMUr1UoD4vasyvQNAgMBAAGgADANBgkqhkiG9w0BAQsF +AAOCAgEAM9VLDurmvoYQyNEpRvFpOLPkgr8rgboo/HN+O/hN9jtmXranLxzTzd+u +OJCujyzS3sbqiZwPeT3APHH4c/mLdrEKZHjfy2sEeXMsVW6dCOcIEYsADSCM6chi +zU86aw4rAkd6YYB+lXFsEmBq78AIpw0vcdpoPoqGRG9ETQsjr4kD3ATGHTnaP551 +61JJed7Kn5FTbieTmzmMa46dn7GjTTmPEcoAnHNCx4CbJAHwWEzvQWF4lVlyb2di +jFD0NQ0WeaFHK/f6UQMqMq+7TpurN8sLWDlyPHA2X/FT+OsUMAX2mLcwZEsYhTjP +dC4ZCuZ/itDgEp3hyPeKiLo+mL/bhhy50nzah/qclI9PS8ufUXEjWoObqiJ5eyIZ +jTZ73qpLupS+Yrami98IYfuOotwGzKkVLwUPtCWQrKsun6YNtotuKKmqEEQX3Fm3 +ZXIYv0BckkXIGd0aKPeMGgMUO26pyxPBSRWB29F07LXzS6eEmfOHvZcT+QLZmys9 +FkH3yePeTilojCnxNINPyKT4Dk0NiZviCdKavUIJ5QtOyDJ1Nc9j5ss+QaAaNtZZ +VTTjupNp+cfCh/kdyGpGP+GgXQQcGgw4OaIbfXqmec7RsqTOppK5gDR4Ne3e5FVm +SpPDyHbv2GJolPG8/HCOsLCJED+wAEfhK/wUg8ZpC+7Ymct2TU8= +-----END CERTIFICATE REQUEST----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient-ecc.crt golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient-ecc.crt --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient-ecc.crt 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient-ecc.crt 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC3TCBxgIJAKElkR1J2tCUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNVBAMMCkdv +IFN3YWdnZXIwHhcNMTgwMTIyMTUyODA3WhcNMTkwMTIyMTUyODA3WjAYMRYwFAYD +VQQDDA1UZXN0IEVDQyBDZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENlON +9ojzKxcMlsAIFcsc+JoMSOd1bREjEHAPaj77L0NjO6p605jT2TTLbj1lpDGD9XRc +vw5iiHvhF41Sl454wjANBgkqhkiG9w0BAQsFAAOCAgEAyYwVGVG/Jzei9zxmWT+k +LeK7OpGzOIyfBdEvQDFSfmyHoVJsR2LdicMJAI0gnVl5V0LdfBpu1MRz6Jdsto+u +RKL1WdygESzWQ/LtqvT4rb5vLhkpCBY6Ua+SoTpY/93iOZpf1ArKOtHzL4Xop2Lb +6/0hHYU73vaBjd9YnA3T0jVFsI/otpfJhSY8FGdKSYKMf6rob9+iv2Tyjm1svkV0 +IBL0D0v/LlGeM8UqXC3ZLaHsTxWi2v6PNfRyFnSNoRX4+I9ejjYvjIKQ9giVcPFQ +SfhR5xm0C0xxYVqoIb6gX6owlmX2duIaV6qjU5YSzwEZqkv0Ze9i+zztBVqBRA7q +fC/AMSxtqo4+Faj+hxX9T4D15hysx76uS7LxCi8GkypSZTGkjhHdMRKa2jIEvW3A +9nKW4nnC5sEBDrOTwaH4Mn6zFik3r9LTfh1gljLu9Ieqizb1gXloFhWJYvC2UwXO +ins3tX2VYBF7p6yIXRmc5nZlpFErGqu2MR/lwJKD6zGIJOzCza/4DP+Mppw+DSPN +XkNJG05uymsaEZceupeBH0uCgVSuVaZ3nfA73RM+0evxsscii/Kw/VFNvNDy5fLg +OQWRm6RlBTK2dRqpsfo9irjdd6NVC0EfqZceYIte/eWn9aPU5uTy/TzRG24ouKtY +Ixs1usnXCabNN/n0AMI+xVc= +-----END CERTIFICATE----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient-ecc.csr golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient-ecc.csr --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient-ecc.csr 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient-ecc.csr 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,7 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIHSMHoCAQAwGDEWMBQGA1UEAwwNVGVzdCBFQ0MgQ2VydDBZMBMGByqGSM49AgEG +CCqGSM49AwEHA0IABDZTjfaI8ysXDJbACBXLHPiaDEjndW0RIxBwD2o++y9DYzuq +etOY09k0y249ZaQxg/V0XL8OYoh74ReNUpeOeMKgADAKBggqhkjOPQQDAgNIADBF +AiEAsqdXJEIuedKkuiavgfc0YXssFWBORAC37F5w+Z0kGEMCIDRGiCaZG4Z/Gutm +id7N5T0Uxah0p5i6OzvCpYPN8f3Y +-----END CERTIFICATE REQUEST----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient-ecc.key golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient-ecc.key --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient-ecc.key 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient-ecc.key 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJyZvFqw3os7TVGOSvK8XM1qysN32jG6G0AQ2mDxcRaaoAoGCCqGSM49 +AwEHoUQDQgAENlON9ojzKxcMlsAIFcsc+JoMSOd1bREjEHAPaj77L0NjO6p605jT +2TTLbj1lpDGD9XRcvw5iiHvhF41Sl454wg== +-----END EC PRIVATE KEY----- diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient.key golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient.key --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient.key 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient.key 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJJwIBAAKCAgEAsaOGkl96Ptu1YI7g4JEkGWJQBTI8w7wHrj1COfViLD0TUvm+ +IHJglKKGx9RDu7aAeKntB2KAoID+zj7DxXYvQO/yUKjAIuR7F/XRvtcZBo6qGTaa +Wv+M5qkCDSkoOhSVUSsEFkrazpRsP+AtcBvG53DEIUonVjt38PDlpOXW+VZmkfN/ +aj39kJ4dd/xe7dowgQ8nv3AbS/M1krtsvWvWVBXMAJ0cCvfgcBAC+BYHhmMqo3zP +S8YLQ3QyloC4cPx8fzBDliJYFsOEM8VXNFhTw9xCN5lIwWo66V7D0pWb69e1wy+1 +4ShcxfBaWoAaW+e/uyyRz9VussklBqizDIqDLVAIrFU7jsR/aJTiQ/vmINjHo2kC +l2ek3fPEcwfJkPG7Ca2gp5qV6fGXlxl4dzq5RTXBeT6ebLtOuBbnT2ZyikfMeP5D +TPIZSD91HCzepc8AZl6Ek+hpgXUXnh6KKd0XXIswwfmiy/LVs2nYRB7M6Vjc/iCp +b1UercJC/K2YNtepLS8/NUCwW7xhrMzcGzSZbZsFesSxqNYNKOu9uF1mOUv2PaOr +BnafPJqfzwzm3lPS9mFy77AqKwBMjXIt1Xeha8uZ0XP9cD0vpTKixOLALUxY+aQ5 +jiZwef8/HvR/jT7A7zXmlnoVuN5zFFLIx8Cbq8M9a0sAxSvVSgPi9qzK9A0CAwEA +AQKCAgAb4VyHsLCRGQ64nvQwitctnL6OcjoTRnm2ISs5yYelBdj4lvX+RbVe3rtk +ta4D0jsLtS/cjts9VcGoQTWc0lXMTVysyC+Pymh/dDd9SmlFHDMaTfWf/qfws+n8 +gs8rfnuJB8VWcl0xOx5aUCcRh2qKfKprxyWxZRgIGucQIHrDG4pxsdP3qs8XWZmq +cVO85RfjyaslYsUGAKAR7ZS9jiVPgTRJjF8QYaM6M2kj4uE/eGUCz94BOI4gAibG +dGF+akJn+/0/nRhSSlF/hqOPNaXAAdvqugYvRSsF4be+X3jfZTXD8sMLGbil4Hlt +5tk8P31aNT6Vbhw3t1Y2W1fuyfaYbPZfprpR/6ZPV3Uf1oWoh1ystIxWnfU7Qdxu +vrkHkrtho6Qt/7d8nNg0mQ8y5glNcVh/iNu9gkyHIpQ2dZpM9tpHArBGweHVDMvZ +vrb/oJ5fRxnKkyouMtWvqO1TY4STPBwCDNSwJa0yxTn3fLvkOdHk1nGEKra7E7Nc +hgsIe4q1ZoEikg7cZe8pvcsHIFfju3Kv/zgDTvHjzHPTdNear7mpk/LihlWdbTiI +UKkgv17JHRsIhfE5G4pZXLRv2qjCGh+uS8yn2k5qPJqGoyIQ2A7BYbaQ/y2gVh6u +hnVdKeETT2uUqIS3xHrV0U9grAeldPJu7bHRwSoJ+HUbp+D8QQKCAQEA4/5K0+Qq +p4rpB+4FpPkMcM4yE1j0ASwHsKGMDPU70/t8yfzZBlpf5WNHTOWa8frnxOyazo8E +sjm2Xw1RlMb21bFF0wjb3uhN2ak++0zIKMf5vWnM0bb2z7yzbcOJVxLzO9DmRUh0 +OXvHvbeKbW9KXHT3YKA2zjaw0mO1zl7sd7r028wYpD6owGtfzooyXwWCnByIQ3nM +JFB7wFJGIg6Kbu2eJULrN1EaT1Ye0FUVmc4x55FLmZvkYziQ88e4UsjYdZ4R5EFi +2XULVI1RA+NPqDXkXmpIx3JnRRvaPc74QatGvDFwY8YeCAjfGFN5LiwFJ6Cz3/jf +WjDLOhqoSiYQ2QKCAQEAx3W7uPE7WNQRsyu2QnsEDdlikgP0FJf3fFZOYurfsxp7 +iqTpNkR9pSWXftP4FBM/KRruoY5TucmPTfGPRS6sQWTfUkVOhrUpOLnuWjf2DJxH +Qqb0wnT76WcAB4L5Gr//91a+w3dwAX5YhdTZLxEKgpm8ky290otCg3+AYOb/P3Ja +V8RR8RQCNV1+y7peBgjj/mbYeVpxjTiZ5cq4cx2OU4rnup/k3HIg1Gw+qr0N9AUN +2WYOL+X0qaKffDa2ekv88H6mVnfRSReFIpteuV0XITwvMc0DbHdj6zEj1TSZMExu +rDVe7eh2BeL1QxbuazRUgwZ+kfy2NUzPkB1SSwi8VQKCAQBs8K4qj0S+Z8avjlFO +Id6K7EvLKN72zGYkRRzZeDiNMwbOsS22NmrJ/eUs3i1qYJxsYS4bcwUocCEvS/rm +XyfEtf8KNppw6YmBbrh0dZzSt7MiibJfptBKNP17fkpau+hTdZ8CDfvTF806XsAb +SGk8wnsNxaBKaqGU9iYCJSNSlpe3is9fc71IrEXMOAaXltdw5sVJkKI12+s121o9 +nbsSBCJj5ZTlCrDKpfj1TSKUKo13+9om3PGFY5sHkTAHBoc/tDcSXRfxllbCoP/M +HsqKMq4bWyfJfWXRBN0EWagQINocxHbShfEFn8+SHRizMj+ITuaEJ7P5sYT6D5DI +VWYJAoIBAEqaxdFiIYGTKN+sbOqm2phXhB/7bJM7WC1glsc29N8n+6ebEUPkEF7y +FZ0xqavQmyJD2ZgCBV0LgBd2T9FfqLx4/3LlS37lSfrWyMlj/xsuZRUQH6KQYR0n +EoK8wXH4+MPJ5WZ1SSa13GSKfYW2SQkaecdPJ54VypYm3ZzhKf3QRuxnGQMkKcNO +KjwHhF2be7PPQg75/lkFH8MstRsRpgengA90+QRfh9oMdtAkEJECRvDW1F2kFIRS +uHacfFp4C67koFDdViGRs5GDLcYFhL5ApaJp/WrXqT7yTWXU26uOGyM8fzpbZbHD +91rVu+3LUAUGK9ds/7Yl+cj8vqgkJ1UCggEAc0a5kmBREz/8rAWKnlCZrhBsxUUM +tiIj32h6dVdFo5SsoyVTxdB394npw1DAsC8xdowrcm/zsYstB3IDMYlrBnCdRxTU +Xu6RD3Jci0Qg1cfLQg5snlRnrNz12wygXcvTvW8cHsda8vO+FL1RgFdehDtYoyZr +swcLLRAOOLTRXy1Xdbv+8vE6s5ryl3uAO+2Zwbmur3tRL+rhXE+Tb308jlt8g2NK +WrRbc3c092aImdGcKmkMGqo6H+xnL9Jj7sR161uO5JJQjxcYbZ5PBmm3J5Z71cSY +LR5snbYdxUy7WKE3yxAoWi+FMsoGf+O77+oHAcpXRaTDv0Enr/7rEku5Yw== +-----END RSA PRIVATE KEY----- Binary files /tmp/tmp4JjccU/uAXbN4tJ8m/golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/myclient.p12 and /tmp/tmp4JjccU/GQRl8VINmw/golang-github-go-openapi-runtime-0.15.0/fixtures/certs/myclient.p12 differ diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/serial golang-github-go-openapi-runtime-0.15.0/fixtures/certs/serial --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/fixtures/certs/serial 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/fixtures/certs/serial 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1 @@ +A125911D49DAD094 diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/flagext/byte_size.go golang-github-go-openapi-runtime-0.15.0/flagext/byte_size.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/flagext/byte_size.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/flagext/byte_size.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,38 @@ +package flagext + +import ( + "github.com/docker/go-units" +) + +// ByteSize used to pass byte sizes to a go-flags CLI +type ByteSize int + +// MarshalFlag implements go-flags Marshaller interface +func (b ByteSize) MarshalFlag() (string, error) { + return units.HumanSize(float64(b)), nil +} + +// UnmarshalFlag implements go-flags Unmarshaller interface +func (b *ByteSize) UnmarshalFlag(value string) error { + sz, err := units.FromHumanSize(value) + if err != nil { + return err + } + *b = ByteSize(int(sz)) + return nil +} + +// String method for a bytesize (pflag value and stringer interface) +func (b ByteSize) String() string { + return units.HumanSize(float64(b)) +} + +// Set the value of this bytesize (pflag value interfaces) +func (b *ByteSize) Set(value string) error { + return b.UnmarshalFlag(value) +} + +// Type returns the type of the pflag value (pflag value interface) +func (b *ByteSize) Type() string { + return "byte-size" +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/flagext/byte_size_test.go golang-github-go-openapi-runtime-0.15.0/flagext/byte_size_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/flagext/byte_size_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/flagext/byte_size_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,43 @@ +package flagext + +import "testing" +import "github.com/stretchr/testify/assert" + +func TestMarshalBytesize(t *testing.T) { + v, err := ByteSize(1024).MarshalFlag() + if assert.NoError(t, err) { + assert.Equal(t, "1.024kB", v) + } +} + +func TestStringBytesize(t *testing.T) { + v := ByteSize(2048).String() + assert.Equal(t, "2.048kB", v) +} + +func TestUnmarshalBytesize(t *testing.T) { + var b ByteSize + err := b.UnmarshalFlag("notASize") + assert.Error(t, err) + + err = b.UnmarshalFlag("1MB") + if assert.NoError(t, err) { + assert.Equal(t, ByteSize(1000000), b) + } +} + +func TestSetBytesize(t *testing.T) { + var b ByteSize + err := b.Set("notASize") + assert.Error(t, err) + + err = b.Set("2MB") + if assert.NoError(t, err) { + assert.Equal(t, ByteSize(2000000), b) + } +} + +func TestTypeBytesize(t *testing.T) { + var b ByteSize + assert.Equal(t, "byte-size", b.Type()) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.gitignore golang-github-go-openapi-runtime-0.15.0/.gitignore --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/.gitignore 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,5 @@ +secrets.yml +coverage.out +*.cov +*.out +playground diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/hack/build-drone.sh golang-github-go-openapi-runtime-0.15.0/hack/build-drone.sh --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/hack/build-drone.sh 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/hack/build-drone.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -#!/bin/bash -set -e -o pipefail - - mkdir -p /drone/{testresults,coverage,dist} - go test -race -timeout 20m -v ./... | go-junit-report -dir /drone/testresults - -# Run test coverage on each subdirectories and merge the coverage profile. -echo "mode: ${GOCOVMODE-count}" > profile.cov - -# Standard go tooling behavior is to ignore dirs with leading underscores -# skip generator for race detection and coverage -for dir in $(go list ./...) -do - pth="$GOPATH/src/$dir" - go test -covermode=${GOCOVMODE-count} -coverprofile=${pth}/profile.out $dir - if [ -f $pth/profile.out ] - then - cat $pth/profile.out | tail -n +2 >> profile.cov - # rm $pth/profile.out - fi -done - -go tool cover -func profile.cov -gocov convert profile.cov | gocov report -gocov convert profile.cov | gocov-html > /drone/coverage/coverage-${CI_BUILD_NUM-"0"}.html \ No newline at end of file diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/hack/coverage golang-github-go-openapi-runtime-0.15.0/hack/coverage --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/hack/coverage 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/hack/coverage 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,20 @@ +#!/bin/bash +set -e -o pipefail + +# Run test coverage on each subdirectories and merge the coverage profile. +echo "mode: ${GOCOVMODE-atomic}" > coverage.txt + +# Standard go tooling behavior is to ignore dirs with leading underscores +# skip generator for race detection and coverage +for dir in $(go list ./...) +do + pth="$GOPATH/src/$dir" + go test -race -timeout 20m -covermode=${GOCOVMODE-atomic} -coverprofile=${pth}/profile.out $dir + if [ -f $pth/profile.out ] + then + cat $pth/profile.out | tail -n +2 >> coverage.txt + rm $pth/profile.out + fi +done + +go tool cover -func coverage.txt diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/hack/gen-self-signed-certs.sh golang-github-go-openapi-runtime-0.15.0/hack/gen-self-signed-certs.sh --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/hack/gen-self-signed-certs.sh 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/hack/gen-self-signed-certs.sh 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,16 @@ +#!/bin/sh + +# generate CA +openssl genrsa -out myCA.key 4096 +openssl req -x509 -new -key myCA.key -out myCA.crt -days 730 -subj /CN="Go Swagger" + +# generate server cert and key +openssl genrsa -out mycert1.key 4096 +openssl req -new -out mycert1.req -key mycert1.key -subj /CN="goswagger.local" +openssl x509 -req -in mycert1.req -out mycert1.crt -CAkey myCA.key -CA myCA.crt -days 365 -CAcreateserial -CAserial serial + +# generate client cert, key and bundle +openssl genrsa -out myclient.key 4096 +openssl req -new -key myclient.key -out myclient.csr +openssl x509 -req -days 730 -in myclient.csr -out myclient.crt -CAkey myCA.key -CA myCA.crt -days 365 -CAcreateserial -CAserial serial +openssl pkcs12 -export -clcerts -in myclient.crt -inkey myclient.key -out myclient.p12 diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/interfaces.go golang-github-go-openapi-runtime-0.15.0/interfaces.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/interfaces.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/interfaces.go 2018-06-28 22:01:56.000000000 +0000 @@ -16,17 +16,11 @@ import ( "io" - "mime/multipart" + "net/http" "github.com/go-openapi/strfmt" ) -// File represents an uploaded file. -type File struct { - Data multipart.File - Header *multipart.FileHeader -} - // OperationHandlerFunc an adapter for a function to the OperationHandler interface type OperationHandlerFunc func(interface{}) (interface{}, error) @@ -85,8 +79,23 @@ Authenticate(interface{}) (bool, interface{}, error) } +// AuthorizerFunc turns a function into an authorizer +type AuthorizerFunc func(*http.Request, interface{}) error + +// Authorize authorizes the processing of the request for the principal +func (f AuthorizerFunc) Authorize(r *http.Request, principal interface{}) error { + return f(r, principal) +} + +// Authorizer represents an authorization strategy +// implementations of Authorizer know how to authorize the principal object +// using the request data and returns error if unauthorized +type Authorizer interface { + Authorize(*http.Request, interface{}) error +} + // Validatable types implementing this interface allow customizing their validation -// this will be used instead of the reflective valditation based on the spec document. +// this will be used instead of the reflective validation based on the spec document. // the implementations are assumed to have been generated by the swagger tool so they should // contain all the validations obtained from the spec type Validatable interface { diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/internal/testing/data.go golang-github-go-openapi-runtime-0.15.0/internal/testing/data.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/internal/testing/data.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/internal/testing/data.go 2018-06-28 22:01:56.000000000 +0000 @@ -341,7 +341,6 @@ }, "definitions": { "Category": { - "id": "Category", "properties": { "id": { "format": "int64", @@ -353,7 +352,6 @@ } }, "Pet": { - "id": "Pet", "properties": { "category": { "$ref": "#/definitions/Category" @@ -408,7 +406,6 @@ ] }, "Tag": { - "id": "Tag", "properties": { "id": { "format": "int64", @@ -617,7 +614,6 @@ }, "definitions": { "Category": { - "id": "Category", "properties": { "id": { "format": "int64", @@ -629,7 +625,6 @@ } }, "Pet": { - "id": "Pet", "properties": { "category": { "$ref": "#/definitions/Category" @@ -684,7 +679,6 @@ ] }, "Tag": { - "id": "Tag", "properties": { "id": { "format": "int64", diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/internal/testing/petstore/api.go golang-github-go-openapi-runtime-0.15.0/internal/testing/petstore/api.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/internal/testing/petstore/api.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/internal/testing/petstore/api.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,7 +15,10 @@ package petstore import ( + goerrors "errors" "io" + "net/http" + "strings" gotest "testing" "github.com/go-openapi/errors" @@ -46,6 +49,8 @@ api.RegisterAuth("basic", security.BasicAuth(func(username, password string) (interface{}, error) { if username == "admin" && password == "admin" { return "admin", nil + } else if username == "topuser" && password == "topuser" { + return "topuser", nil } return nil, errors.Unauthenticated("basic") })) @@ -55,6 +60,12 @@ } return nil, errors.Unauthenticated("token") })) + api.RegisterAuthorizer(runtime.AuthorizerFunc(func(r *http.Request, user interface{}) error { + if r.Method == http.MethodPost && strings.HasPrefix(r.URL.Path, "/api/pets") && user.(string) != "admin" { + return goerrors.New("unauthorized") + } + return nil + })) api.RegisterOperation("get", "/pets", new(stubOperationHandler)) api.RegisterOperation("post", "/pets", new(stubOperationHandler)) api.RegisterOperation("delete", "/pets/{id}", new(stubOperationHandler)) @@ -94,6 +105,7 @@ } return nil, errors.Unauthenticated("token") })) + api.RegisterAuthorizer(security.Authorized()) api.RegisterOperation("get", "/pets", new(stubOperationHandler)) api.RegisterOperation("post", "/pets", new(stubOperationHandler)) api.RegisterOperation("delete", "/pets/{id}", new(stubOperationHandler)) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/internal/testing/simplepetstore/api.go golang-github-go-openapi-runtime-0.15.0/internal/testing/simplepetstore/api.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/internal/testing/simplepetstore/api.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/internal/testing/simplepetstore/api.go 2018-06-28 22:01:56.000000000 +0000 @@ -294,10 +294,10 @@ }, "definitions": { "pet": { - "required": [ - "id", - "name" - ], + "required": [ + "name", + "status" + ], "properties": { "id": { "type": "integer", @@ -306,20 +306,23 @@ "name": { "type": "string" }, - "tag": { + "status": { "type": "string" - } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } } }, "petInput": { "allOf": [ { - "$ref": "pet" + "$ref": "#/definitions/pet" }, { - "required": [ - "name" - ], "properties": { "id": { "type": "integer", diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/logger/logger.go golang-github-go-openapi-runtime-0.15.0/logger/logger.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/logger/logger.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/logger/logger.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,12 @@ +package logger + +import "os" + +type Logger interface { + Printf(format string, args ...interface{}) + Debugf(format string, args ...interface{}) +} + +func DebugEnabled() bool { + return os.Getenv("SWAGGER_DEBUG") != "" || os.Getenv("DEBUG") != "" +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/logger/standard.go golang-github-go-openapi-runtime-0.15.0/logger/standard.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/logger/standard.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/logger/standard.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,22 @@ +package logger + +import ( + "fmt" + "os" +) + +type StandardLogger struct{} + +func (StandardLogger) Printf(format string, args ...interface{}) { + if len(format) == 0 || format[len(format)-1] != '\n' { + format += "\n" + } + fmt.Fprintf(os.Stderr, format, args...) +} + +func (StandardLogger) Debugf(format string, args ...interface{}) { + if len(format) == 0 || format[len(format)-1] != '\n' { + format += "\n" + } + fmt.Fprintf(os.Stderr, format, args...) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/body_test.go golang-github-go-openapi-runtime-0.15.0/middleware/body_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/body_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/body_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -3,6 +3,7 @@ import ( "io" "net/http" + "path" "testing" "github.com/go-openapi/runtime" @@ -33,13 +34,13 @@ api.DefaultConsumes = runtime.JSONMime ctx.router = DefaultRouter(spec, ctx.api) - req, err := http.NewRequest("GET", "/pets", new(eofReader)) + req, err := http.NewRequest("GET", path.Join(spec.BasePath(), "/pets"), new(eofReader)) if assert.NoError(t, err) { req.Header.Set("Content-Type", runtime.JSONMime) - ri, ok := ctx.RouteInfo(req) + ri, rCtx, ok := ctx.RouteInfo(req) if assert.True(t, ok) { - + req = rCtx err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error { defer r.Body.Close() var data interface{} @@ -60,31 +61,31 @@ api.DefaultConsumes = runtime.JSONMime ctx.router = DefaultRouter(spec, ctx.api) - req, err := http.NewRequest("DELETE", "/pets/123", new(eofReader)) + req, err := http.NewRequest("DELETE", path.Join(spec.BasePath(), "/pets/123"), new(eofReader)) if assert.NoError(t, err) { req.Header.Set("Accept", "*/*") - ri, ok := ctx.RouteInfo(req) + ri, rCtx, ok := ctx.RouteInfo(req) if assert.True(t, ok) { - - err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error { + req = rCtx + bverr := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error { return nil })) - assert.NoError(t, err) - //assert.Equal(t, io.EOF, err) + assert.NoError(t, bverr) + //assert.Equal(t, io.EOF, bverr) } } - req, err = http.NewRequest("DELETE", "/pets/123", new(eofReader)) + req, err = http.NewRequest("DELETE", path.Join(spec.BasePath(), "/pets/123"), new(eofReader)) if assert.NoError(t, err) { req.Header.Set("Accept", "*/*") req.Header.Set("Content-Type", runtime.JSONMime) req.ContentLength = 1 - ri, ok := ctx.RouteInfo(req) + ri, rCtx, ok := ctx.RouteInfo(req) if assert.True(t, ok) { - + req = rCtx err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error { defer r.Body.Close() var data interface{} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/context.go golang-github-go-openapi-runtime-0.15.0/middleware/context.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/context.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/context.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,20 +15,31 @@ package middleware import ( + stdContext "context" "net/http" "strings" + "sync" "github.com/go-openapi/analysis" "github.com/go-openapi/errors" "github.com/go-openapi/loads" "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/logger" "github.com/go-openapi/runtime/middleware/untyped" - "github.com/go-openapi/runtime/security" "github.com/go-openapi/spec" "github.com/go-openapi/strfmt" - "github.com/gorilla/context" ) +// Debug when true turns on verbose logging +var Debug = logger.DebugEnabled() +var Logger logger.Logger = logger.StandardLogger{} + +func debugLog(format string, args ...interface{}) { + if Debug { + Logger.Printf(format, args...) + } +} + // A Builder can create middlewares type Builder func(http.Handler) http.Handler @@ -56,17 +67,18 @@ } // Context is a type safe wrapper around an untyped request context -// used throughout to store request context with the gorilla context module +// used throughout to store request context with the standard context attached +// to the http.Request type Context struct { spec *loads.Document analyzer *analysis.Spec api RoutableAPI router Router - formats strfmt.Registry } type routableUntypedAPI struct { api *untyped.API + hlock *sync.Mutex handlers map[string]map[string]http.Handler defaultConsumes string defaultProduces string @@ -81,7 +93,7 @@ for method, hls := range analyzer.Operations() { um := strings.ToUpper(method) for path, op := range hls { - schemes := analyzer.SecurityDefinitionsFor(op) + schemes := analyzer.SecurityRequirementsFor(op) if oh, ok := api.OperationHandlerFor(method, path); ok { if handlers == nil { @@ -91,12 +103,17 @@ handlers[um] = make(map[string]http.Handler) } - handlers[um][path] = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // lookup route info in the context - route, _ := context.RouteInfo(r) + route, rCtx, _ := context.RouteInfo(r) + if rCtx != nil { + r = rCtx + } // bind and validate the request using reflection - bound, validation := context.BindAndValidate(r, route) + var bound interface{} + var validation error + bound, r, validation = context.BindAndValidate(r, route) if validation != nil { context.Respond(w, r, route.Produces, route, validation) return @@ -115,14 +132,16 @@ }) if len(schemes) > 0 { - handlers[um][path] = newSecureAPI(context, handlers[um][path]) + handler = newSecureAPI(context, handler) } + handlers[um][path] = handler } } } return &routableUntypedAPI{ api: api, + hlock: new(sync.Mutex), handlers: handlers, defaultProduces: api.DefaultProduces, defaultConsumes: api.DefaultConsumes, @@ -130,11 +149,14 @@ } func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) { + r.hlock.Lock() paths, ok := r.handlers[strings.ToUpper(method)] if !ok { + r.hlock.Unlock() return nil, false } handler, ok := paths[path] + r.hlock.Unlock() return handler, ok } func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) { @@ -149,6 +171,9 @@ func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator { return r.api.AuthenticatorsFor(schemes) } +func (r *routableUntypedAPI) Authorizer() runtime.Authorizer { + return r.api.Authorizer() +} func (r *routableUntypedAPI) Formats() strfmt.Registry { return r.api.Formats() } @@ -167,7 +192,7 @@ if spec != nil { an = analysis.New(spec.Spec()) } - ctx := &Context{spec: spec, api: routableAPI, analyzer: an} + ctx := &Context{spec: spec, api: routableAPI, analyzer: an, router: routes} return ctx } @@ -179,6 +204,7 @@ } ctx := &Context{spec: spec, analyzer: an} ctx.api = newRoutableUntypedAPI(spec, api, ctx) + ctx.router = routes return ctx } @@ -201,14 +227,37 @@ ctxContentType ctxResponseFormat ctxMatchedRoute - ctxAllowedMethods ctxBoundParams ctxSecurityPrincipal ctxSecurityScopes - - ctxConsumer ) +// MatchedRouteFrom request context value. +func MatchedRouteFrom(req *http.Request) *MatchedRoute { + mr := req.Context().Value(ctxMatchedRoute) + if mr == nil { + return nil + } + if res, ok := mr.(*MatchedRoute); ok { + return res + } + return nil +} + +// SecurityPrincipalFrom request context value. +func SecurityPrincipalFrom(req *http.Request) interface{} { + return req.Context().Value(ctxSecurityPrincipal) +} + +// SecurityScopesFrom request context value. +func SecurityScopesFrom(req *http.Request) []string { + rs := req.Context().Value(ctxSecurityScopes) + if res, ok := rs.([]string); ok { + return res + } + return nil +} + type contentTypeValue struct { MediaType string Charset string @@ -274,108 +323,140 @@ } // ContentType gets the parsed value of a content type -func (c *Context) ContentType(request *http.Request) (string, string, error) { - if v, ok := context.GetOk(request, ctxContentType); ok { - if val, ok := v.(*contentTypeValue); ok { - return val.MediaType, val.Charset, nil - } +// Returns the media type, its charset and a shallow copy of the request +// when its context doesn't contain the content type value, otherwise it returns +// the same request +// Returns the error that runtime.ContentType may retunrs. +func (c *Context) ContentType(request *http.Request) (string, string, *http.Request, error) { + var rCtx = request.Context() + + if v, ok := rCtx.Value(ctxContentType).(*contentTypeValue); ok { + return v.MediaType, v.Charset, request, nil } mt, cs, err := runtime.ContentType(request.Header) if err != nil { - return "", "", err + return "", "", nil, err } - context.Set(request, ctxContentType, &contentTypeValue{mt, cs}) - return mt, cs, nil + rCtx = stdContext.WithValue(rCtx, ctxContentType, &contentTypeValue{mt, cs}) + return mt, cs, request.WithContext(rCtx), nil } // LookupRoute looks a route up and returns true when it is found func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) { - if route, ok := c.router.Lookup(request.Method, request.URL.Path); ok { + if route, ok := c.router.Lookup(request.Method, request.URL.EscapedPath()); ok { return route, ok } return nil, false } // RouteInfo tries to match a route for this request -func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, bool) { - if v, ok := context.GetOk(request, ctxMatchedRoute); ok { - if val, ok := v.(*MatchedRoute); ok { - return val, ok - } +// Returns the matched route, a shallow copy of the request if its context +// contains the matched router, otherwise the same request, and a bool to +// indicate if it the request matches one of the routes, if it doesn't +// then it returns false and nil for the other two return values +func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, *http.Request, bool) { + var rCtx = request.Context() + + if v, ok := rCtx.Value(ctxMatchedRoute).(*MatchedRoute); ok { + return v, request, ok } if route, ok := c.LookupRoute(request); ok { - context.Set(request, ctxMatchedRoute, route) - return route, ok + rCtx = stdContext.WithValue(rCtx, ctxMatchedRoute, route) + return route, request.WithContext(rCtx), ok } - return nil, false + return nil, nil, false } // ResponseFormat negotiates the response content type -func (c *Context) ResponseFormat(r *http.Request, offers []string) string { - if v, ok := context.GetOk(r, ctxResponseFormat); ok { - if val, ok := v.(string); ok { - return val - } +// Returns the response format and a shallow copy of the request if its context +// doesn't contain the response format, otherwise the same request +func (c *Context) ResponseFormat(r *http.Request, offers []string) (string, *http.Request) { + var rCtx = r.Context() + + if v, ok := rCtx.Value(ctxResponseFormat).(string); ok { + debugLog("[%s %s] found response format %q in context", r.Method, r.URL.Path, v) + return v, r } format := NegotiateContentType(r, offers, "") if format != "" { - context.Set(r, ctxResponseFormat, format) + debugLog("[%s %s] set response format %q in context", r.Method, r.URL.Path, format) + r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format)) } - return format + debugLog("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format) + return format, r } // AllowedMethods gets the allowed methods for the path of this request func (c *Context) AllowedMethods(request *http.Request) []string { - return c.router.OtherMethods(request.Method, request.URL.Path) + return c.router.OtherMethods(request.Method, request.URL.EscapedPath()) +} + +// ResetAuth removes the current principal from the request context +func (c *Context) ResetAuth(request *http.Request) *http.Request { + rctx := request.Context() + rctx = stdContext.WithValue(rctx, ctxSecurityPrincipal, nil) + rctx = stdContext.WithValue(rctx, ctxSecurityScopes, nil) + return request.WithContext(rctx) } // Authorize authorizes the request -func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, error) { - if len(route.Authenticators) == 0 { - return nil, nil +// Returns the principal object and a shallow copy of the request when its +// context doesn't contain the principal, otherwise the same request or an error +// (the last) if one of the authenticators returns one or an Unauthenticated error +func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, *http.Request, error) { + if route == nil || !route.HasAuth() { + return nil, nil, nil } - if v, ok := context.GetOk(request, ctxSecurityPrincipal); ok { - return v, nil + + var rCtx = request.Context() + if v := rCtx.Value(ctxSecurityPrincipal); v != nil { + return v, request, nil } - for scheme, authenticator := range route.Authenticators { - applies, usr, err := authenticator.Authenticate(&security.ScopedAuthRequest{ - Request: request, - RequiredScopes: route.Scopes[scheme], - }) - if !applies || err != nil || usr == nil { - continue + applies, usr, err := route.Authenticators.Authenticate(request, route) + if !applies || err != nil || !route.Authenticators.AllowsAnonymous() && usr == nil { + if err != nil { + return nil, nil, err + } + return nil, nil, errors.Unauthenticated("invalid credentials") + } + if route.Authorizer != nil { + if err := route.Authorizer.Authorize(request, usr); err != nil { + return nil, nil, errors.New(http.StatusForbidden, err.Error()) } - context.Set(request, ctxSecurityPrincipal, usr) - context.Set(request, ctxSecurityScopes, route.Scopes[scheme]) - return usr, nil } - return nil, errors.Unauthenticated("invalid credentials") + rCtx = stdContext.WithValue(rCtx, ctxSecurityPrincipal, usr) + rCtx = stdContext.WithValue(rCtx, ctxSecurityScopes, route.Authenticator.AllScopes()) + return usr, request.WithContext(rCtx), nil } // BindAndValidate binds and validates the request -func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, error) { - if v, ok := context.GetOk(request, ctxBoundParams); ok { - if val, ok := v.(*validation); ok { - if len(val.result) > 0 { - return val.bound, errors.CompositeValidationError(val.result...) - } - return val.bound, nil +// Returns the validation map and a shallow copy of the request when its context +// doesn't contain the validation, otherwise it returns the same request or an +// CompositeValidationError error +func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, *http.Request, error) { + var rCtx = request.Context() + + if v, ok := rCtx.Value(ctxBoundParams).(*validation); ok { + debugLog("got cached validation (valid: %t)", len(v.result) == 0) + if len(v.result) > 0 { + return v.bound, request, errors.CompositeValidationError(v.result...) } + return v.bound, request, nil } result := validateRequest(c, request, matched) - if result != nil { - context.Set(request, ctxBoundParams, result) - } + rCtx = stdContext.WithValue(rCtx, ctxBoundParams, result) + request = request.WithContext(rCtx) if len(result.result) > 0 { - return result.bound, errors.CompositeValidationError(result.result...) + return result.bound, request, errors.CompositeValidationError(result.result...) } - return result.bound, nil + debugLog("no validation errors found") + return result.bound, request, nil } // NotFound the default not found responder for when no route has been matched yet @@ -385,6 +466,7 @@ // Respond renders the response after doing some content negotiation func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) { + debugLog("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces) offers := []string{} for _, mt := range produces { if mt != c.api.DefaultProduces() { @@ -393,15 +475,17 @@ } // the default producer is last so more specific producers take precedence offers = append(offers, c.api.DefaultProduces()) + debugLog("offers: %v", offers) - format := c.ResponseFormat(r, offers) + var format string + format, r = c.ResponseFormat(r, offers) rw.Header().Set(runtime.HeaderContentType, format) if resp, ok := data.(Responder); ok { producers := route.Producers prod, ok := producers[format] if !ok { - prods := c.api.ProducersFor([]string{c.api.DefaultProduces()}) + prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()})) pr, ok := prods[c.api.DefaultProduces()] if !ok { panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format)) @@ -429,7 +513,7 @@ if r.Method == "HEAD" { return } - producers := c.api.ProducersFor(offers) + producers := c.api.ProducersFor(normalizeOffers(offers)) prod, ok := producers[format] if !ok { panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format)) @@ -450,7 +534,7 @@ prod, ok := producers[format] if !ok { if !ok { - prods := c.api.ProducersFor([]string{c.api.DefaultProduces()}) + prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()})) pr, ok := prods[c.api.DefaultProduces()] if !ok { panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format)) @@ -467,11 +551,32 @@ c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response")) } -// APIHandler returns a handler to serve +// APIHandler returns a handler to serve the API, this includes a swagger spec, router and the contract defined in the swagger spec func (c *Context) APIHandler(builder Builder) http.Handler { b := builder if b == nil { b = PassthroughBuilder } - return specMiddleware(c, newRouter(c, b(newOperationExecutor(c)))) + + var title string + sp := c.spec.Spec() + if sp != nil && sp.Info != nil && sp.Info.Title != "" { + title = sp.Info.Title + } + + redocOpts := RedocOpts{ + BasePath: c.BasePath(), + Title: title, + } + + return Spec("", c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b))) +} + +// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec +func (c *Context) RoutesHandler(builder Builder) http.Handler { + b := builder + if b == nil { + b = PassthroughBuilder + } + return NewRouter(c, b(NewOperationExecutor(c))) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/context_test.go golang-github-go-openapi-runtime-0.15.0/middleware/context_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/context_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/context_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -25,7 +25,6 @@ "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/internal/testing/petstore" "github.com/go-openapi/runtime/middleware/untyped" - "github.com/gorilla/context" "github.com/stretchr/testify/assert" ) @@ -40,13 +39,6 @@ return nil, nil } -type testBinder struct { -} - -func (t *testBinder) BindRequest(r *http.Request, m *MatchedRoute) error { - return nil -} - func init() { loads.AddLoader(fmts.YAMLMatcher, fmts.YAMLDoc) } @@ -92,44 +84,77 @@ ctx := NewContext(spec, api, nil) ctx.router = DefaultRouter(spec, ctx.api) - request, _ := runtime.JSONRequest("GET", "/pets", nil) - - v, ok := context.GetOk(request, ctxSecurityPrincipal) - assert.False(t, ok) - assert.Nil(t, v) + request, _ := runtime.JSONRequest("GET", "/api/pets", nil) - ri, ok := ctx.RouteInfo(request) + ri, reqWithCtx, ok := ctx.RouteInfo(request) assert.True(t, ok) - p, err := ctx.Authorize(request, ri) + assert.NotNil(t, reqWithCtx) + + request = reqWithCtx + + p, reqWithCtx, err := ctx.Authorize(request, ri) assert.Error(t, err) assert.Nil(t, p) + assert.Nil(t, reqWithCtx) - v, ok = context.GetOk(request, ctxSecurityPrincipal) - assert.False(t, ok) + v := request.Context().Value(ctxSecurityPrincipal) assert.Nil(t, v) request.SetBasicAuth("wrong", "wrong") - p, err = ctx.Authorize(request, ri) + p, reqWithCtx, err = ctx.Authorize(request, ri) assert.Error(t, err) assert.Nil(t, p) + assert.Nil(t, reqWithCtx) - v, ok = context.GetOk(request, ctxSecurityPrincipal) - assert.False(t, ok) + v = request.Context().Value(ctxSecurityPrincipal) assert.Nil(t, v) request.SetBasicAuth("admin", "admin") - p, err = ctx.Authorize(request, ri) + p, reqWithCtx, err = ctx.Authorize(request, ri) assert.NoError(t, err) assert.Equal(t, "admin", p) + assert.NotNil(t, reqWithCtx) - v, ok = context.GetOk(request, ctxSecurityPrincipal) + // Assign the new returned request to follow with the test + request = reqWithCtx + + v, ok = request.Context().Value(ctxSecurityPrincipal).(string) assert.True(t, ok) assert.Equal(t, "admin", v) + // Once the request context contains the principal the authentication + // isn't rechecked request.SetBasicAuth("doesn't matter", "doesn't") - pp, rr := ctx.Authorize(request, ri) + pp, reqCtx, rr := ctx.Authorize(request, ri) assert.Equal(t, p, pp) assert.Equal(t, err, rr) + assert.Equal(t, request, reqCtx) +} + +func TestContextAuthorize_WithAuthorizer(t *testing.T) { + spec, api := petstore.NewAPI(t) + ctx := NewContext(spec, api, nil) + ctx.router = DefaultRouter(spec, ctx.api) + + request, _ := runtime.JSONRequest("POST", "/api/pets", nil) + + ri, reqWithCtx, ok := ctx.RouteInfo(request) + assert.True(t, ok) + assert.NotNil(t, reqWithCtx) + + request = reqWithCtx + + request.SetBasicAuth("topuser", "topuser") + p, reqWithCtx, err := ctx.Authorize(request, ri) + assert.Error(t, err) + assert.Nil(t, p) + assert.Nil(t, reqWithCtx) + + request.SetBasicAuth("admin", "admin") + p, reqWithCtx, err = ctx.Authorize(request, ri) + assert.NoError(t, err) + assert.Equal(t, "admin", p) + assert.NotNil(t, reqWithCtx) } func TestContextNegotiateContentType(t *testing.T) { @@ -137,21 +162,17 @@ ctx := NewContext(spec, api, nil) ctx.router = DefaultRouter(spec, ctx.api) - request, _ := http.NewRequest("POST", "/pets", nil) + request, _ := http.NewRequest("POST", "/api/pets", nil) // request.Header.Add("Accept", "*/*") request.Header.Add("content-type", "text/html") - v, ok := context.GetOk(request, ctxBoundParams) - assert.False(t, ok) + v := request.Context().Value(ctxBoundParams) assert.Nil(t, v) - ri, _ := ctx.RouteInfo(request) + ri, request, _ := ctx.RouteInfo(request) - res := NegotiateContentType(request, ri.Produces, "") - assert.Equal(t, "", res) - - res2 := NegotiateContentType(request, ri.Produces, "text/plain") - assert.Equal(t, "text/plain", res2) + res := NegotiateContentType(request, ri.Produces, "text/plain") + assert.Equal(t, ri.Produces[0], res) } func TestContextBindAndValidate(t *testing.T) { @@ -159,27 +180,27 @@ ctx := NewContext(spec, api, nil) ctx.router = DefaultRouter(spec, ctx.api) - request, _ := http.NewRequest("POST", "/pets", nil) + request, _ := http.NewRequest("POST", "/api/pets", nil) request.Header.Add("Accept", "*/*") request.Header.Add("content-type", "text/html") request.ContentLength = 1 - v, ok := context.GetOk(request, ctxBoundParams) - assert.False(t, ok) + v := request.Context().Value(ctxBoundParams) assert.Nil(t, v) - ri, _ := ctx.RouteInfo(request) - data, result := ctx.BindAndValidate(request, ri) // this requires a much more thorough test + ri, request, _ := ctx.RouteInfo(request) + data, request, result := ctx.BindAndValidate(request, ri) // this requires a much more thorough test assert.NotNil(t, data) assert.NotNil(t, result) - v, ok = context.GetOk(request, ctxBoundParams) + v, ok := request.Context().Value(ctxBoundParams).(*validation) assert.True(t, ok) assert.NotNil(t, v) - dd, rr := ctx.BindAndValidate(request, ri) + dd, rCtx, rr := ctx.BindAndValidate(request, ri) assert.Equal(t, data, dd) assert.Equal(t, result, rr) + assert.Equal(t, rCtx, request) } func TestContextRender(t *testing.T) { @@ -191,9 +212,9 @@ ctx := NewContext(spec, api, nil) ctx.router = DefaultRouter(spec, ctx.api) - request, _ := http.NewRequest("GET", "pets", nil) + request, _ := http.NewRequest("GET", "/api/pets", nil) request.Header.Set(runtime.HeaderAccept, ct) - ri, _ := ctx.RouteInfo(request) + ri, request, _ := ctx.RouteInfo(request) recorder := httptest.NewRecorder() ctx.Respond(recorder, request, []string{ct}, ri, map[string]interface{}{"name": "hello"}) @@ -204,16 +225,18 @@ ctx.Respond(recorder, request, []string{ct}, ri, errors.New("this went wrong")) assert.Equal(t, 500, recorder.Code) - recorder = httptest.NewRecorder() - assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) }) + // recorder = httptest.NewRecorder() + // assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) }) + // Panic when route is nil and there is not a producer for the requested response format recorder = httptest.NewRecorder() - request, _ = http.NewRequest("GET", "pets", nil) - assert.Panics(t, func() { ctx.Respond(recorder, request, []string{}, ri, map[string]interface{}{"name": "hello"}) }) + request, _ = http.NewRequest("GET", "/api/pets", nil) + request.Header.Set(runtime.HeaderAccept, "text/xml") + assert.Panics(t, func() { ctx.Respond(recorder, request, []string{}, nil, map[string]interface{}{"name": "hello"}) }) - request, _ = http.NewRequest("GET", "/pets", nil) + request, _ = http.NewRequest("GET", "/api/pets", nil) request.Header.Set(runtime.HeaderAccept, ct) - ri, _ = ctx.RouteInfo(request) + ri, request, _ = ctx.RouteInfo(request) recorder = httptest.NewRecorder() ctx.Respond(recorder, request, []string{ct}, ri, map[string]interface{}{"name": "hello"}) @@ -224,18 +247,19 @@ ctx.Respond(recorder, request, []string{ct}, ri, errors.New("this went wrong")) assert.Equal(t, 500, recorder.Code) - recorder = httptest.NewRecorder() - assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) }) + // recorder = httptest.NewRecorder() + // assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) }) // recorder = httptest.NewRecorder() // request, _ = http.NewRequest("GET", "/pets", nil) // assert.Panics(t, func() { ctx.Respond(recorder, request, []string{}, ri, map[string]interface{}{"name": "hello"}) }) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("DELETE", "/pets/1", nil) - ri, _ = ctx.RouteInfo(request) + request, _ = http.NewRequest("DELETE", "/api/pets/1", nil) + ri, request, _ = ctx.RouteInfo(request) ctx.Respond(recorder, request, ri.Produces, ri, nil) assert.Equal(t, 204, recorder.Code) + } func TestContextValidResponseFormat(t *testing.T) { @@ -248,21 +272,21 @@ request.Header.Set(runtime.HeaderAccept, ct) // check there's nothing there - cached, ok := context.GetOk(request, ctxResponseFormat) + cached, ok := request.Context().Value(ctxResponseFormat).(string) assert.False(t, ok) assert.Empty(t, cached) // trigger the parse - mt := ctx.ResponseFormat(request, []string{ct}) + mt, request := ctx.ResponseFormat(request, []string{ct}) assert.Equal(t, ct, mt) // check it was cached - cached, ok = context.GetOk(request, ctxResponseFormat) + cached, ok = request.Context().Value(ctxResponseFormat).(string) assert.True(t, ok) assert.Equal(t, ct, cached) // check if the cast works and fetch from cache too - mt = ctx.ResponseFormat(request, []string{ct}) + mt, _ = ctx.ResponseFormat(request, []string{ct}) assert.Equal(t, ct, mt) } @@ -277,22 +301,23 @@ request.Header.Set(runtime.HeaderAccept, ct) // check there's nothing there - cached, ok := context.GetOk(request, ctxResponseFormat) + cached, ok := request.Context().Value(ctxResponseFormat).(string) assert.False(t, ok) assert.Empty(t, cached) // trigger the parse - mt := ctx.ResponseFormat(request, []string{other}) + mt, request := ctx.ResponseFormat(request, []string{other}) assert.Empty(t, mt) // check it was cached - cached, ok = context.GetOk(request, ctxResponseFormat) + cached, ok = request.Context().Value(ctxResponseFormat).(string) assert.False(t, ok) assert.Empty(t, cached) // check if the cast works and fetch from cache too - mt = ctx.ResponseFormat(request, []string{other}) + mt, rCtx := ctx.ResponseFormat(request, []string{other}) assert.Empty(t, mt) + assert.Equal(t, request, rCtx) } func TestContextValidRoute(t *testing.T) { @@ -300,23 +325,28 @@ ctx := NewContext(spec, api, nil) ctx.router = DefaultRouter(spec, ctx.api) - request, _ := http.NewRequest("GET", "/pets", nil) + request, _ := http.NewRequest("GET", "/api/pets", nil) // check there's nothing there - _, ok := context.GetOk(request, ctxMatchedRoute) - assert.False(t, ok) + cached := request.Context().Value(ctxMatchedRoute) + assert.Nil(t, cached) - matched, ok := ctx.RouteInfo(request) + matched, rCtx, ok := ctx.RouteInfo(request) assert.True(t, ok) assert.NotNil(t, matched) + assert.NotNil(t, rCtx) + assert.NotEqual(t, request, rCtx) + + request = rCtx // check it was cached - _, ok = context.GetOk(request, ctxMatchedRoute) + _, ok = request.Context().Value(ctxMatchedRoute).(*MatchedRoute) assert.True(t, ok) - matched, ok = ctx.RouteInfo(request) + matched, rCtx, ok = ctx.RouteInfo(request) assert.True(t, ok) assert.NotNil(t, matched) + assert.Equal(t, request, rCtx) } func TestContextInvalidRoute(t *testing.T) { @@ -327,20 +357,22 @@ request, _ := http.NewRequest("DELETE", "pets", nil) // check there's nothing there - _, ok := context.GetOk(request, ctxMatchedRoute) - assert.False(t, ok) + cached := request.Context().Value(ctxMatchedRoute) + assert.Nil(t, cached) - matched, ok := ctx.RouteInfo(request) + matched, rCtx, ok := ctx.RouteInfo(request) assert.False(t, ok) assert.Nil(t, matched) + assert.Nil(t, rCtx) - // check it was cached - _, ok = context.GetOk(request, ctxMatchedRoute) - assert.False(t, ok) + // check it was not cached + cached = request.Context().Value(ctxMatchedRoute) + assert.Nil(t, cached) - matched, ok = ctx.RouteInfo(request) + matched, rCtx, ok = ctx.RouteInfo(request) assert.False(t, ok) assert.Nil(t, matched) + assert.Nil(t, rCtx) } func TestContextValidContentType(t *testing.T) { @@ -351,22 +383,27 @@ request.Header.Set(runtime.HeaderContentType, ct) // check there's nothing there - _, ok := context.GetOk(request, ctxContentType) - assert.False(t, ok) + cached := request.Context().Value(ctxContentType) + assert.Nil(t, cached) // trigger the parse - mt, _, err := ctx.ContentType(request) + mt, _, rCtx, err := ctx.ContentType(request) assert.NoError(t, err) assert.Equal(t, ct, mt) + assert.NotNil(t, rCtx) + assert.NotEqual(t, request, rCtx) + + request = rCtx // check it was cached - _, ok = context.GetOk(request, ctxContentType) - assert.True(t, ok) + cached = request.Context().Value(ctxContentType) + assert.NotNil(t, cached) // check if the cast works and fetch from cache too - mt, _, err = ctx.ContentType(request) + mt, _, rCtx, err = ctx.ContentType(request) assert.NoError(t, err) assert.Equal(t, ct, mt) + assert.Equal(t, request, rCtx) } func TestContextInvalidContentType(t *testing.T) { @@ -377,19 +414,21 @@ request.Header.Set(runtime.HeaderContentType, ct) // check there's nothing there - _, ok := context.GetOk(request, ctxContentType) - assert.False(t, ok) + cached := request.Context().Value(ctxContentType) + assert.Nil(t, cached) // trigger the parse - mt, _, err := ctx.ContentType(request) + mt, _, rCtx, err := ctx.ContentType(request) assert.Error(t, err) assert.Empty(t, mt) + assert.Nil(t, rCtx) // check it was not cached - _, ok = context.GetOk(request, ctxContentType) - assert.False(t, ok) + cached = request.Context().Value(ctxContentType) + assert.Nil(t, cached) // check if the failure continues - _, _, err = ctx.ContentType(request) + _, _, rCtx, err = ctx.ContentType(request) assert.Error(t, err) + assert.Nil(t, rCtx) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/denco/router.go golang-github-go-openapi-runtime-0.15.0/middleware/denco/router.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/denco/router.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/denco/router.go 2018-06-28 22:01:56.000000000 +0000 @@ -43,7 +43,7 @@ // Lookup returns data and path parameters that associated with path. // params is a slice of the Param that arranged in the order in which parameters appeared. -// e.g. when built routing path is "/path/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}]. +// e.g. when built routing path is "/path/to/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}]. func (rt *Router) Lookup(path string) (data interface{}, params Params, found bool) { if data, found := rt.static[path]; found { return data, nil, true diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/denco/router_test.go golang-github-go-openapi-runtime-0.15.0/middleware/denco/router_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/denco/router_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/denco/router_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -230,11 +230,13 @@ {"/networks/:owner/:repo/events", "testroute0"}, {"/orgs/:org/events", "testroute1"}, {"/notifications/threads/:id", "testroute2"}, + {"/mypathisgreat/:thing-id", "testroute3"}, } testcases = []testcase{ {"/networks/:owner/:repo/events", "testroute0", []denco.Param{{"owner", ":owner"}, {"repo", ":repo"}}, true}, {"/orgs/:org/events", "testroute1", []denco.Param{{"org", ":org"}}, true}, {"/notifications/threads/:id", "testroute2", []denco.Param{{"id", ":id"}}, true}, + {"/mypathisgreat/:thing-id", "testroute3", []denco.Param{{"thing-id", ":thing-id"}}, true}, } runLookupTest(t, records, testcases) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/denco/server_test.go golang-github-go-openapi-runtime-0.15.0/middleware/denco/server_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/denco/server_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/denco/server_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -24,7 +24,7 @@ mux.PUT("/user/:name", testHandlerFunc), mux.Handler("GET", "/user/handler", testHandlerFunc), mux.Handler("POST", "/user/handler", testHandlerFunc), - {"PUT", "/user/inference", testHandlerFunc}, + mux.Handler("PUT", "/user/inference", testHandlerFunc), }) if err != nil { t.Fatal(err) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/doc.go golang-github-go-openapi-runtime-0.15.0/middleware/doc.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/doc.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/doc.go 2018-06-28 22:01:56.000000000 +0000 @@ -20,17 +20,14 @@ "net/http" "github.com/go-openapi/errors" - "github.com/gorilla/context" ) func newCompleteMiddleware(ctx *Context) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - defer context.Clear(r) - // use context to lookup routes if matched, ok := ctx.RouteInfo(r); ok { - if len(matched.Authenticators) > 0 { + if matched.NeedsAuth() { if _, err := ctx.Authorize(r, matched); err != nil { ctx.Respond(rw, r, matched.Produces, matched, err) return diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/go18.go golang-github-go-openapi-runtime-0.15.0/middleware/go18.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/go18.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/go18.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,9 @@ +// +build go1.8 + +package middleware + +import "net/url" + +func pathUnescape(path string) (string, error) { + return url.PathUnescape(path) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/header/header.go golang-github-go-openapi-runtime-0.15.0/middleware/header/header.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/header/header.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/header/header.go 2018-06-28 22:01:56.000000000 +0000 @@ -46,8 +46,8 @@ var t octetType isCtl := c <= 31 || c == 127 isChar := 0 <= c && c <= 127 - isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 - if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { + isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) + if strings.ContainsRune(" \t\r\n", rune(c)) { t |= isSpace } if isChar && !isCtl && !isSeparator { @@ -134,9 +134,12 @@ // ParseValueAndParams parses a comma separated list of values with optional // semicolon separated name-value pairs. Content-Type and Content-Disposition // headers are in this format. -func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) { +func ParseValueAndParams(header http.Header, key string) (string, map[string]string) { + return parseValueAndParams(header.Get(key)) +} + +func parseValueAndParams(s string) (value string, params map[string]string) { params = make(map[string]string) - s := header.Get(key) value, s = expectTokenSlash(s) if value == "" { return @@ -164,11 +167,33 @@ return } +// AcceptSpec ... type AcceptSpec struct { Value string Q float64 } +// ParseAccept2 ... +func ParseAccept2(header http.Header, key string) (specs []AcceptSpec) { + for _, en := range ParseList(header, key) { + v, p := parseValueAndParams(en) + var spec AcceptSpec + spec.Value = v + spec.Q = 1.0 + if p != nil { + if q, ok := p["q"]; ok { + spec.Q, _ = expectQuality(q) + } + } + if spec.Q < 0.0 { + continue + } + specs = append(specs, spec) + } + + return +} + // ParseAccept parses Accept* headers. func ParseAccept(header http.Header, key string) (specs []AcceptSpec) { loop: @@ -183,12 +208,14 @@ s = skipSpace(s) if strings.HasPrefix(s, ";") { s = skipSpace(s[1:]) - if !strings.HasPrefix(s, "q=") { - continue loop + for !strings.HasPrefix(s, "q=") && s != "" && !strings.HasPrefix(s, ",") { + s = skipSpace(s[1:]) } - spec.Q, s = expectQuality(s[2:]) - if spec.Q < 0.0 { - continue loop + if strings.HasPrefix(s, "q=") { + spec.Q, s = expectQuality(s[2:]) + if spec.Q < 0.0 { + continue loop + } } } specs = append(specs, spec) @@ -282,14 +309,14 @@ case escape: escape = false p[j] = b - j += 1 + j++ case b == '\\': escape = true case b == '"': return string(p[:j]), s[i+1:] default: p[j] = b - j += 1 + j++ } } return "", "" diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/negotiate.go golang-github-go-openapi-runtime-0.15.0/middleware/negotiate.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/negotiate.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/negotiate.go 2018-06-28 22:01:56.000000000 +0000 @@ -48,7 +48,12 @@ bestQ := -1.0 bestWild := 3 specs := header.ParseAccept(r.Header, "Accept") - for _, offer := range offers { + for _, rawOffer := range offers { + offer := normalizeOffer(rawOffer) + // No Accept header: just return the first offer. + if len(specs) == 0 { + return rawOffer + } for _, spec := range specs { switch { case spec.Q == 0.0: @@ -59,24 +64,35 @@ if spec.Q > bestQ || bestWild > 2 { bestQ = spec.Q bestWild = 2 - bestOffer = offer + bestOffer = rawOffer } case strings.HasSuffix(spec.Value, "/*"): if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) && (spec.Q > bestQ || bestWild > 1) { bestQ = spec.Q bestWild = 1 - bestOffer = offer + bestOffer = rawOffer } default: if spec.Value == offer && (spec.Q > bestQ || bestWild > 0) { bestQ = spec.Q bestWild = 0 - bestOffer = offer + bestOffer = rawOffer } } } } return bestOffer } + +func normalizeOffers(orig []string) (norm []string) { + for _, o := range orig { + norm = append(norm, normalizeOffer(o)) + } + return +} + +func normalizeOffer(orig string) string { + return strings.SplitN(orig, ";", 2)[0] +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/negotiate_test.go golang-github-go-openapi-runtime-0.15.0/middleware/negotiate_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/negotiate_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/negotiate_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -50,6 +50,7 @@ {"text/html;q=0.5, image/png", []string{"text/html"}, "", "text/html"}, {"text/html;q=0.5, image/png", []string{"image/png", "text/html"}, "", "image/png"}, {"text/html;q=0.5, image/png", []string{"text/html", "image/png"}, "", "image/png"}, + {"text/html;q=0.5, image/png", []string{"text/html", "image/png"}, "", "image/png"}, {"image/png, image/*;q=0.5", []string{"image/jpg", "image/png"}, "", "image/png"}, {"image/png, image/*;q=0.5", []string{"image/jpg"}, "", "image/jpg"}, {"image/png, image/*;q=0.5", []string{"image/jpg", "image/gif"}, "", "image/jpg"}, @@ -57,6 +58,9 @@ {"image/png, image/*", []string{"image/gif", "image/jpg"}, "", "image/gif"}, {"image/png, image/*", []string{"image/gif", "image/png"}, "", "image/png"}, {"image/png, image/*", []string{"image/png", "image/gif"}, "", "image/png"}, + {"application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.7,text/plain;version=0.0.4;q=0.3", []string{"text/plain"}, "", "text/plain"}, + {"application/json", []string{"application/json; charset=utf-8", "image/png"}, "", "application/json; charset=utf-8"}, + {"application/json; charset=utf-8", []string{"application/json; charset=utf-8", "image/png"}, "", "application/json; charset=utf-8"}, } func TestNegotiateContentType(t *testing.T) { @@ -68,3 +72,12 @@ } } } + +func TestNegotiateContentTypeNoAcceptHeader(t *testing.T) { + r := &http.Request{Header: http.Header{}} + offers := []string{"application/json", "text/xml"} + actual := NegotiateContentType(r, offers, "") + if actual != "application/json" { + t.Errorf("NegotiateContentType(empty, %#v, empty)=%q, want %q", offers, actual, "application/json") + } +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/operation.go golang-github-go-openapi-runtime-0.15.0/middleware/operation.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/operation.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/operation.go 2018-06-28 22:01:56.000000000 +0000 @@ -16,10 +16,15 @@ import "net/http" -func newOperationExecutor(ctx *Context) http.Handler { +// NewOperationExecutor creates a context aware middleware that handles the operations after routing +func NewOperationExecutor(ctx *Context) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { // use context to lookup routes - route, _ := ctx.RouteInfo(r) + route, rCtx, _ := ctx.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + route.Handler.ServeHTTP(rw, r) }) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/operation_test.go golang-github-go-openapi-runtime-0.15.0/middleware/operation_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/operation_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/operation_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -35,10 +35,10 @@ context := NewContext(spec, api, nil) context.router = DefaultRouter(spec, context.api) - mw := newOperationExecutor(context) + mw := NewOperationExecutor(context) recorder := httptest.NewRecorder() - request, _ := http.NewRequest("GET", "/pets", nil) + request, _ := http.NewRequest("GET", "/api/pets", nil) request.Header.Add("Accept", "application/json") request.SetBasicAuth("admin", "admin") mw.ServeHTTP(recorder, request) @@ -52,10 +52,10 @@ context = NewContext(spec, api, nil) context.router = DefaultRouter(spec, context.api) - mw = newOperationExecutor(context) + mw = NewOperationExecutor(context) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("GET", "/pets", nil) + request, _ = http.NewRequest("GET", "/api/pets", nil) request.Header.Add("Accept", "application/json") request.SetBasicAuth("admin", "admin") mw.ServeHTTP(recorder, request) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/parameter.go golang-github-go-openapi-runtime-0.15.0/middleware/parameter.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/parameter.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/parameter.go 2018-06-28 22:01:56.000000000 +0000 @@ -193,28 +193,28 @@ } if mt == "multipart/form-data" { - if err := request.ParseMultipartForm(defaultMaxMemory); err != nil { + if err = request.ParseMultipartForm(defaultMaxMemory); err != nil { return errors.NewParseError(p.Name, p.parameter.In, "", err) } } - if err := request.ParseForm(); err != nil { + if err = request.ParseForm(); err != nil { return errors.NewParseError(p.Name, p.parameter.In, "", err) } if p.parameter.Type == "file" { - file, header, err := request.FormFile(p.parameter.Name) - if err != nil { - return errors.NewParseError(p.Name, p.parameter.In, "", err) + file, header, ffErr := request.FormFile(p.parameter.Name) + if ffErr != nil { + return errors.NewParseError(p.Name, p.parameter.In, "", ffErr) } target.Set(reflect.ValueOf(runtime.File{Data: file, Header: header})) return nil } if request.MultipartForm != nil { - data, custom, hasKey, err := p.readValue(runtime.Values(request.MultipartForm.Value), target) - if err != nil { - return err + data, custom, hasKey, rvErr := p.readValue(runtime.Values(request.MultipartForm.Value), target) + if rvErr != nil { + return rvErr } if custom { return nil @@ -330,7 +330,8 @@ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if data == "" { if target.CanSet() { - target.SetInt(defVal.Int()) + rd := defVal.Convert(reflect.TypeOf(int64(0))) + target.SetInt(rd.Int()) } return nil } @@ -348,7 +349,8 @@ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if data == "" { if target.CanSet() { - target.SetUint(defVal.Uint()) + rd := defVal.Convert(reflect.TypeOf(uint64(0))) + target.SetUint(rd.Uint()) } return nil } @@ -366,7 +368,8 @@ case reflect.Float32, reflect.Float64: if data == "" { if target.CanSet() { - target.SetFloat(defVal.Float()) + rd := defVal.Convert(reflect.TypeOf(float64(0))) + target.SetFloat(rd.Float()) } return nil } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/parameter_test.go golang-github-go-openapi-runtime-0.15.0/middleware/parameter_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/parameter_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/parameter_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -28,9 +28,9 @@ "github.com/stretchr/testify/assert" ) -type email struct { - Address string -} +// type email struct { +// Address string +// } type paramFactory func(string) *spec.Parameter diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/pre_go18.go golang-github-go-openapi-runtime-0.15.0/middleware/pre_go18.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/pre_go18.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/pre_go18.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,9 @@ +// +build !go1.8 + +package middleware + +import "net/url" + +func pathUnescape(path string) (string, error) { + return url.QueryUnescape(path) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/redoc.go golang-github-go-openapi-runtime-0.15.0/middleware/redoc.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/redoc.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/redoc.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,101 @@ +package middleware + +import ( + "bytes" + "fmt" + "html/template" + "net/http" + "path" +) + +// RedocOpts configures the Redoc middlewares +type RedocOpts struct { + // BasePath for the UI path, defaults to: / + BasePath string + // Path combines with BasePath for the full UI path, defaults to: docs + Path string + // SpecURL the url to find the spec for + SpecURL string + // RedocURL for the js that generates the redoc site, defaults to: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js + RedocURL string + // Title for the documentation site, default to: API documentation + Title string +} + +// EnsureDefaults in case some options are missing +func (r *RedocOpts) EnsureDefaults() { + if r.BasePath == "" { + r.BasePath = "/" + } + if r.Path == "" { + r.Path = "docs" + } + if r.SpecURL == "" { + r.SpecURL = "/swagger.json" + } + if r.RedocURL == "" { + r.RedocURL = redocLatest + } + if r.Title == "" { + r.Title = "API documentation" + } +} + +// Redoc creates a middleware to serve a documentation site for a swagger spec. +// This allows for altering the spec before starting the http listener. +// +func Redoc(opts RedocOpts, next http.Handler) http.Handler { + opts.EnsureDefaults() + + pth := path.Join(opts.BasePath, opts.Path) + tmpl := template.Must(template.New("redoc").Parse(redocTemplate)) + + buf := bytes.NewBuffer(nil) + _ = tmpl.Execute(buf, opts) + b := buf.Bytes() + + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + if r.URL.Path == pth { + rw.Header().Set("Content-Type", "text/html; charset=utf-8") + rw.WriteHeader(http.StatusOK) + + _, _ = rw.Write(b) + return + } + + if next == nil { + rw.Header().Set("Content-Type", "text/plain") + rw.WriteHeader(http.StatusNotFound) + _, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth))) + return + } + next.ServeHTTP(rw, r) + }) +} + +const ( + redocLatest = "https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js" + redocTemplate = ` + + + {{ .Title }} + + + + + + + + + + + +` +) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/redoc_test.go golang-github-go-openapi-runtime-0.15.0/middleware/redoc_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/redoc_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/redoc_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,22 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRedocMiddleware(t *testing.T) { + redoc := Redoc(RedocOpts{}, nil) + + req, _ := http.NewRequest("GET", "/docs", nil) + recorder := httptest.NewRecorder() + redoc.ServeHTTP(recorder, req) + assert.Equal(t, 200, recorder.Code) + assert.Equal(t, "text/html; charset=utf-8", recorder.Header().Get("Content-Type")) + assert.Contains(t, recorder.Body.String(), "API documentation") + assert.Contains(t, recorder.Body.String(), "") + assert.Contains(t, recorder.Body.String(), redocLatest) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/request.go golang-github-go-openapi-runtime-0.15.0/middleware/request.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/request.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/request.go 2018-06-28 22:01:56.000000000 +0000 @@ -51,10 +51,10 @@ val := reflect.Indirect(reflect.ValueOf(data)) isMap := val.Kind() == reflect.Map var result []error - + debugLog("binding %d parameters for %s %s", len(o.Parameters), request.Method, request.URL.EscapedPath()) for fieldName, param := range o.Parameters { binder := o.paramBinders[fieldName] - + debugLog("binding parameter %s for %s %s", fieldName, request.Method, request.URL.EscapedPath()) var target reflect.Value if !isMap { binder.Name = fieldName diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/request_test.go golang-github-go-openapi-runtime-0.15.0/middleware/request_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/request_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/request_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -67,21 +67,6 @@ Friend []friend } -type jsonRequestAllTypes struct { - Confirmed bool - Planned strfmt.Date - Delivered strfmt.DateTime - Age int32 - ID int64 - Score float32 - Factor float64 - Friend friend - Name string - Tags []string - Picture []byte - RequestID int64 -} - func parametersForAllTypes(fmt string) map[string]spec.Parameter { if fmt == "" { fmt = "csv" @@ -299,7 +284,7 @@ binder := newUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default) lval := []string{"one", "two", "three"} - queryString := "" + var queryString string switch fmt { case "multi": queryString = strings.Join(lval, "&tags=") @@ -428,8 +413,8 @@ part, err := writer.CreateFormFile("file", "plain-jane.txt") assert.NoError(t, err) - part.Write([]byte("the file contents")) - writer.WriteField("name", "the-name") + _, _ = part.Write([]byte("the file contents")) + _ = writer.WriteField("name", "the-name") assert.NoError(t, writer.Close()) urlStr := "http://localhost:8002/hello" @@ -462,8 +447,8 @@ part, err = writer.CreateFormFile("bad-name", "plain-jane.txt") assert.NoError(t, err) - part.Write([]byte("the file contents")) - writer.WriteField("name", "the-name") + _, _ = part.Write([]byte("the file contents")) + _ = writer.WriteField("name", "the-name") assert.NoError(t, writer.Close()) req, _ = http.NewRequest("POST", urlStr, body) req.Header.Set("Content-Type", writer.FormDataContentType()) @@ -473,7 +458,7 @@ req, _ = http.NewRequest("POST", urlStr, body) req.Header.Set("Content-Type", writer.FormDataContentType()) - req.MultipartReader() + _, _ = req.MultipartReader() data = fileRequest{} assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data)) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/route_authenticator_test.go golang-github-go-openapi-runtime-0.15.0/middleware/route_authenticator_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/route_authenticator_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/route_authenticator_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,258 @@ +package middleware + +import ( + "errors" + "net/http" + "testing" + + "github.com/go-openapi/runtime" + "github.com/stretchr/testify/require" +) + +type countAuthenticator struct { + count int + applies bool + principal interface{} + err error +} + +func (c *countAuthenticator) Authenticate(params interface{}) (bool, interface{}, error) { + c.count++ + return c.applies, c.principal, c.err +} + +func newCountAuthenticator(applies bool, principal interface{}, err error) *countAuthenticator { + return &countAuthenticator{applies: applies, principal: principal, err: err} +} + +var ( + successAuth = runtime.AuthenticatorFunc(func(_ interface{}) (bool, interface{}, error) { + return true, "the user", nil + }) + failAuth = runtime.AuthenticatorFunc(func(_ interface{}) (bool, interface{}, error) { + return true, nil, errors.New("unauthenticated") + }) + noApplyAuth = runtime.AuthenticatorFunc(func(_ interface{}) (bool, interface{}, error) { + return false, nil, nil + }) +) + +func TestAuthenticateSingle(t *testing.T) { + ra := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth1": successAuth, + }, + Schemes: []string{"auth1"}, + Scopes: map[string][]string{"auth1": nil}, + } + ras := RouteAuthenticators([]RouteAuthenticator{ra}) + + require.False(t, ras.AllowsAnonymous()) + + req, _ := http.NewRequest("GET", "/", nil) + route := &MatchedRoute{} + ok, prin, err := ras.Authenticate(req, route) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, "the user", prin) + + require.Equal(t, ra, *route.Authenticator) +} + +func TestAuthenticateLogicalOr(t *testing.T) { + ra1 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth1": noApplyAuth, + }, + Schemes: []string{"auth1"}, + Scopes: map[string][]string{"auth1": nil}, + } + ra2 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth2": successAuth, + }, + Schemes: []string{"auth2"}, + Scopes: map[string][]string{"auth2": nil}, + } + // right side matches + ras := RouteAuthenticators([]RouteAuthenticator{ra1, ra2}) + + require.False(t, ras.AllowsAnonymous()) + + req, _ := http.NewRequest("GET", "/", nil) + route := &MatchedRoute{} + ok, prin, err := ras.Authenticate(req, route) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, "the user", prin) + + require.Equal(t, ra2, *route.Authenticator) + + // left side matches + ras = RouteAuthenticators([]RouteAuthenticator{ra2, ra1}) + + require.False(t, ras.AllowsAnonymous()) + + req, _ = http.NewRequest("GET", "/", nil) + route = &MatchedRoute{} + ok, prin, err = ras.Authenticate(req, route) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, "the user", prin) + + require.Equal(t, ra2, *route.Authenticator) +} + +func TestAuthenticateLogicalAnd(t *testing.T) { + ra1 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth1": noApplyAuth, + }, + Schemes: []string{"auth1"}, + Scopes: map[string][]string{"auth1": nil}, + } + auther := newCountAuthenticator(true, "the user", nil) + ra2 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth2": auther, + "auth3": auther, + }, + Schemes: []string{"auth2", "auth3"}, + Scopes: map[string][]string{"auth2": nil}, + } + ras := RouteAuthenticators([]RouteAuthenticator{ra1, ra2}) + + require.False(t, ras.AllowsAnonymous()) + + req, _ := http.NewRequest("GET", "/", nil) + route := &MatchedRoute{} + ok, prin, err := ras.Authenticate(req, route) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, "the user", prin) + + require.Equal(t, ra2, *route.Authenticator) + require.Equal(t, 2, auther.count) + + var count int + successA := runtime.AuthenticatorFunc(func(_ interface{}) (bool, interface{}, error) { + count++ + return true, "the user", nil + }) + failA := runtime.AuthenticatorFunc(func(_ interface{}) (bool, interface{}, error) { + count++ + return true, nil, errors.New("unauthenticated") + }) + + ra3 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth2": successA, + "auth3": failA, + "auth4": successA, + }, + Schemes: []string{"auth2", "auth3", "auth4"}, + Scopes: map[string][]string{"auth2": nil}, + } + ras = RouteAuthenticators([]RouteAuthenticator{ra1, ra3}) + + require.False(t, ras.AllowsAnonymous()) + + req, _ = http.NewRequest("GET", "/", nil) + route = &MatchedRoute{} + ok, prin, err = ras.Authenticate(req, route) + require.Error(t, err) + require.True(t, ok) + require.Nil(t, prin) + + require.Equal(t, ra3, *route.Authenticator) + require.Equal(t, 2, count) + + ra4 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth2": successA, + "auth3": successA, + "auth4": failA, + }, + Schemes: []string{"auth2", "auth3", "auth4"}, + Scopes: map[string][]string{"auth2": nil}, + } + ras = RouteAuthenticators([]RouteAuthenticator{ra1, ra4}) + + require.False(t, ras.AllowsAnonymous()) + + req, _ = http.NewRequest("GET", "/", nil) + route = &MatchedRoute{} + ok, prin, err = ras.Authenticate(req, route) + require.Error(t, err) + require.True(t, ok) + require.Nil(t, prin) + + require.Equal(t, ra4, *route.Authenticator) + require.Equal(t, 5, count) +} + +func TestAuthenticateOptional(t *testing.T) { + ra1 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth1": noApplyAuth, + }, + Schemes: []string{"auth1"}, + Scopes: map[string][]string{"auth1": nil}, + } + ra2 := RouteAuthenticator{ + allowAnonymous: true, + Schemes: []string{""}, + Scopes: map[string][]string{"": []string{}}, + } + + ra3 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth2": noApplyAuth, + }, + Schemes: []string{"auth2"}, + Scopes: map[string][]string{"auth2": nil}, + } + + ras := RouteAuthenticators([]RouteAuthenticator{ra1, ra2, ra3}) + require.True(t, ras.AllowsAnonymous()) + + req, _ := http.NewRequest("GET", "/", nil) + route := &MatchedRoute{} + ok, prin, err := ras.Authenticate(req, route) + require.NoError(t, err) + require.True(t, ok) + require.Nil(t, prin) + + require.Equal(t, ra2, *route.Authenticator) + + ra4 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth1": noApplyAuth, + }, + Schemes: []string{"auth1"}, + Scopes: map[string][]string{"auth1": nil}, + } + ra5 := RouteAuthenticator{ + allowAnonymous: true, + } + + ra6 := RouteAuthenticator{ + Authenticator: map[string]runtime.Authenticator{ + "auth2": failAuth, + }, + Schemes: []string{"auth2"}, + Scopes: map[string][]string{"auth2": nil}, + } + + ras = RouteAuthenticators([]RouteAuthenticator{ra4, ra5, ra6}) + require.True(t, ras.AllowsAnonymous()) + + req, _ = http.NewRequest("GET", "/", nil) + route = &MatchedRoute{} + ok, prin, err = ras.Authenticate(req, route) + require.Error(t, err) + require.True(t, ok) + require.Nil(t, prin) + + require.Equal(t, ra6, *route.Authenticator) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/router.go golang-github-go-openapi-runtime-0.15.0/middleware/router.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/router.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/router.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,10 +15,14 @@ package middleware import ( + "fmt" "net/http" + fpath "path" "regexp" "strings" + "github.com/go-openapi/runtime/security" + "github.com/go-openapi/analysis" "github.com/go-openapi/errors" "github.com/go-openapi/loads" @@ -26,7 +30,6 @@ "github.com/go-openapi/runtime/middleware/denco" "github.com/go-openapi/spec" "github.com/go-openapi/strfmt" - "github.com/gorilla/context" ) // RouteParam is a object to capture route params in a framework agnostic way. @@ -62,41 +65,25 @@ return nil, false, false } -func newRouter(ctx *Context, next http.Handler) http.Handler { +// NewRouter creates a new context aware router middleware +func NewRouter(ctx *Context, next http.Handler) http.Handler { if ctx.router == nil { ctx.router = DefaultRouter(ctx.spec, ctx.api) } - basePath := ctx.spec.BasePath() - isRoot := basePath == "" || basePath == "/" - for strings.HasSuffix(basePath, "/") { - basePath = basePath[:len(basePath)-1] - } return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - defer context.Clear(r) - // use context to lookup routes - if isRoot { - if _, ok := ctx.RouteInfo(r); ok { - next.ServeHTTP(rw, r) - return - } - } else { - ep := r.URL.EscapedPath() - if p := strings.TrimPrefix(ep, basePath); len(p) < len(ep) { - r.URL.Path = p - if _, ok := ctx.RouteInfo(r); ok { - next.ServeHTTP(rw, r) - return - } - } + if _, rCtx, ok := ctx.RouteInfo(r); ok { + next.ServeHTTP(rw, rCtx) + return } + // Not found, check if it exists in the other methods first if others := ctx.AllowedMethods(r); len(others) > 0 { ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others)) return } - ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.Path)) + ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.EscapedPath())) }) } @@ -108,6 +95,7 @@ ConsumersFor([]string) map[string]runtime.Consumer ProducersFor([]string) map[string]runtime.Producer AuthenticatorsFor(map[string]spec.SecurityScheme) map[string]runtime.Authenticator + Authorizer() runtime.Authorizer Formats() strfmt.Registry DefaultProduces() string DefaultConsumes() string @@ -128,7 +116,6 @@ type defaultRouter struct { spec *loads.Document - api RoutableAPI routers map[string]*denco.Router } @@ -147,13 +134,152 @@ if spec != nil { for method, paths := range builder.analyzer.Operations() { for path, operation := range paths { - builder.AddRoute(method, path, operation) + fp := fpath.Join(spec.BasePath(), path) + debugLog("adding route %s %s %q", method, fp, operation.ID) + builder.AddRoute(method, fp, operation) } } } return builder.Build() } +// RouteAuthenticator is an authenticator that can compose several authenticators together. +// It also knows when it contains an authenticator that allows for anonymous pass through. +// Contains a group of 1 or more authenticators that have a logical AND relationship +type RouteAuthenticator struct { + Authenticator map[string]runtime.Authenticator + Schemes []string + Scopes map[string][]string + allScopes []string + commonScopes []string + allowAnonymous bool +} + +func (ra *RouteAuthenticator) AllowsAnonymous() bool { + return ra.allowAnonymous +} + +// AllScopes returns a list of unique scopes that is the combination +// of all the scopes in the requirements +func (ra *RouteAuthenticator) AllScopes() []string { + return ra.allScopes +} + +// CommonScopes returns a list of unique scopes that are common in all the +// scopes in the requirements +func (ra *RouteAuthenticator) CommonScopes() []string { + return ra.commonScopes +} + +// Authenticate Authenticator interface implementation +func (ra *RouteAuthenticator) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) { + if ra.allowAnonymous { + route.Authenticator = ra + return true, nil, nil + } + // iterate in proper order + var lastResult interface{} + for _, scheme := range ra.Schemes { + if authenticator, ok := ra.Authenticator[scheme]; ok { + applies, princ, err := authenticator.Authenticate(&security.ScopedAuthRequest{ + Request: req, + RequiredScopes: ra.Scopes[scheme], + }) + if !applies { + return false, nil, nil + } + + if err != nil { + route.Authenticator = ra + return true, nil, err + } + lastResult = princ + } + } + route.Authenticator = ra + return true, lastResult, nil +} + +func stringSliceUnion(slices ...[]string) []string { + unique := make(map[string]struct{}) + var result []string + for _, slice := range slices { + for _, entry := range slice { + if _, ok := unique[entry]; ok { + continue + } + unique[entry] = struct{}{} + result = append(result, entry) + } + } + return result +} + +func stringSliceIntersection(slices ...[]string) []string { + unique := make(map[string]int) + var intersection []string + + total := len(slices) + var emptyCnt int + for _, slice := range slices { + if len(slice) == 0 { + emptyCnt++ + continue + } + + for _, entry := range slice { + unique[entry]++ + if unique[entry] == total-emptyCnt { // this entry appeared in all the non-empty slices + intersection = append(intersection, entry) + } + } + } + + return intersection +} + +// RouteAuthenticators represents a group of authenticators that represent a logical OR +type RouteAuthenticators []RouteAuthenticator + +// AllowsAnonymous returns true when there is an authenticator that means optional auth +func (ras RouteAuthenticators) AllowsAnonymous() bool { + for _, ra := range ras { + if ra.AllowsAnonymous() { + return true + } + } + return false +} + +// Authenticate method implemention so this collection can be used as authenticator +func (ras RouteAuthenticators) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) { + var lastError error + var allowsAnon bool + var anonAuth RouteAuthenticator + + for _, ra := range ras { + if ra.AllowsAnonymous() { + anonAuth = ra + allowsAnon = true + continue + } + applies, usr, err := ra.Authenticate(req, route) + if !applies || err != nil || usr == nil { + if err != nil { + lastError = err + } + continue + } + return applies, usr, nil + } + + if allowsAnon && lastError == nil { + route.Authenticator = &anonAuth + return true, nil, lastError + } + return lastError != nil, nil, lastError +} + type routeEntry struct { PathPattern string BasePath string @@ -166,29 +292,73 @@ Handler http.Handler Formats strfmt.Registry Binder *untypedRequestBinder - Authenticators map[string]runtime.Authenticator - Scopes map[string][]string + Authenticators RouteAuthenticators + Authorizer runtime.Authorizer } // MatchedRoute represents the route that was matched in this request type MatchedRoute struct { routeEntry - Params RouteParams - Consumer runtime.Consumer - Producer runtime.Producer + Params RouteParams + Consumer runtime.Consumer + Producer runtime.Producer + Authenticator *RouteAuthenticator +} + +// HasAuth returns true when the route has a security requirement defined +func (m *MatchedRoute) HasAuth() bool { + return len(m.Authenticators) > 0 +} + +// NeedsAuth returns true when the request still +// needs to perform authentication +func (m *MatchedRoute) NeedsAuth() bool { + return m.HasAuth() && m.Authenticator == nil } func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) { - if router, ok := d.routers[strings.ToUpper(method)]; ok { - if m, rp, ok := router.Lookup(path); ok && m != nil { + mth := strings.ToUpper(method) + debugLog("looking up route for %s %s", method, path) + if Debug { + if len(d.routers) == 0 { + debugLog("there are no known routers") + } + for meth := range d.routers { + debugLog("got a router for %s", meth) + } + } + if router, ok := d.routers[mth]; ok { + if m, rp, ok := router.Lookup(fpath.Clean(path)); ok && m != nil { if entry, ok := m.(*routeEntry); ok { + debugLog("found a route for %s %s with %d parameters", method, path, len(entry.Parameters)) var params RouteParams for _, p := range rp { - params = append(params, RouteParam{Name: p.Name, Value: p.Value}) + v, err := pathUnescape(p.Value) + if err != nil { + debugLog("failed to escape %q: %v", p.Value, err) + v = p.Value + } + // a workaround to handle fragment/composing parameters until they are supported in denco router + // check if this parameter is a fragment within a path segment + if xpos := strings.Index(entry.PathPattern, fmt.Sprintf("{%s}", p.Name)) + len(p.Name) + 2; xpos < len(entry.PathPattern) && entry.PathPattern[xpos] != '/' { + // extract fragment parameters + ep := strings.Split(entry.PathPattern[xpos:], "/")[0] + pnames, pvalues := decodeCompositParams(p.Name, v, ep, nil, nil) + for i, pname := range pnames { + params = append(params, RouteParam{Name: pname, Value: pvalues[i]}) + } + } else { + // use the parameter directly + params = append(params, RouteParam{Name: p.Name, Value: v}) + } } return &MatchedRoute{routeEntry: *entry, Params: params}, true } + } else { + debugLog("couldn't find a route by path for %s %s", method, path) } + } else { + debugLog("couldn't find a route by method for %s %s", method, path) } return nil, false } @@ -198,7 +368,7 @@ var methods []string for k, v := range d.routers { if k != mn { - if _, _, ok := v.Lookup(path); ok { + if _, _, ok := v.Lookup(fpath.Clean(path)); ok { methods = append(methods, k) continue } @@ -207,44 +377,98 @@ return methods } -var pathConverter = regexp.MustCompile(`{(\w+)}`) +// convert swagger parameters per path segment into a denco parameter as multiple parameters per segment are not supported in denco +var pathConverter = regexp.MustCompile(`{(.+?)}([^/]*)`) + +func decodeCompositParams(name string, value string, pattern string, names []string, values []string) ([]string, []string) { + pleft := strings.Index(pattern, "{") + names = append(names, name) + if pleft < 0 { + if strings.HasSuffix(value, pattern) { + values = append(values, value[:len(value)-len(pattern)]) + } else { + values = append(values, "") + } + } else { + toskip := pattern[:pleft] + pright := strings.Index(pattern, "}") + vright := strings.Index(value, toskip) + if vright >= 0 { + values = append(values, value[:vright]) + } else { + values = append(values, "") + value = "" + } + return decodeCompositParams(pattern[pleft+1:pright], value[vright+len(toskip):], pattern[pright+1:], names, values) + } + return names, values +} func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Operation) { mn := strings.ToUpper(method) - if handler, ok := d.api.HandlerFor(method, path); ok { + bp := fpath.Clean(d.spec.BasePath()) + if len(bp) > 0 && bp[len(bp)-1] == '/' { + bp = bp[:len(bp)-1] + } + + debugLog("operation: %#v", *operation) + if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok { consumes := d.analyzer.ConsumesFor(operation) produces := d.analyzer.ProducesFor(operation) - parameters := d.analyzer.ParamsFor(method, path) - definitions := d.analyzer.SecurityDefinitionsFor(operation) - requirements := d.analyzer.SecurityRequirementsFor(operation) - scopes := make(map[string][]string, len(requirements)) - for _, v := range requirements { - scopes[v.Name] = v.Scopes - } + parameters := d.analyzer.ParamsFor(method, strings.TrimPrefix(path, bp)) record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{ + BasePath: bp, + PathPattern: path, Operation: operation, Handler: handler, Consumes: consumes, Produces: produces, - Consumers: d.api.ConsumersFor(consumes), - Producers: d.api.ProducersFor(produces), + Consumers: d.api.ConsumersFor(normalizeOffers(consumes)), + Producers: d.api.ProducersFor(normalizeOffers(produces)), Parameters: parameters, Formats: d.api.Formats(), Binder: newUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()), - Authenticators: d.api.AuthenticatorsFor(definitions), - Scopes: scopes, + Authenticators: d.buildAuthenticators(operation), + Authorizer: d.api.Authorizer(), }) d.records[mn] = append(d.records[mn], record) } } +func (d *defaultRouteBuilder) buildAuthenticators(operation *spec.Operation) RouteAuthenticators { + requirements := d.analyzer.SecurityRequirementsFor(operation) + var auths []RouteAuthenticator + for _, reqs := range requirements { + var schemes []string + scopes := make(map[string][]string, len(reqs)) + var scopeSlices [][]string + for _, req := range reqs { + schemes = append(schemes, req.Name) + scopes[req.Name] = req.Scopes + scopeSlices = append(scopeSlices, req.Scopes) + } + + definitions := d.analyzer.SecurityDefinitionsForRequirements(reqs) + authenticators := d.api.AuthenticatorsFor(definitions) + auths = append(auths, RouteAuthenticator{ + Authenticator: authenticators, + Schemes: schemes, + Scopes: scopes, + allScopes: stringSliceUnion(scopeSlices...), + commonScopes: stringSliceIntersection(scopeSlices...), + allowAnonymous: len(reqs) == 1 && reqs[0].Name == "", + }) + } + return auths +} + func (d *defaultRouteBuilder) Build() *defaultRouter { routers := make(map[string]*denco.Router) for method, records := range d.records { router := denco.New() - router.Build(records) + _ = router.Build(records) routers[method] = router } return &defaultRouter{ diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/router_test.go golang-github-go-openapi-runtime-0.15.0/middleware/router_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/router_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/router_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -35,7 +35,7 @@ func TestRouterMiddleware(t *testing.T) { spec, api := petstore.NewAPI(t) context := NewContext(spec, api, nil) - mw := newRouter(context, http.HandlerFunc(terminator)) + mw := NewRouter(context, http.HandlerFunc(terminator)) recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/api/pets", nil) @@ -59,9 +59,15 @@ mw.ServeHTTP(recorder, request) assert.Equal(t, http.StatusNotFound, recorder.Code) + recorder = httptest.NewRecorder() + request, _ = http.NewRequest("GET", "/pets", nil) + + mw.ServeHTTP(recorder, request) + assert.Equal(t, http.StatusNotFound, recorder.Code) + spec, api = petstore.NewRootAPI(t) context = NewContext(spec, api, nil) - mw = newRouter(context, http.HandlerFunc(terminator)) + mw = NewRouter(context, http.HandlerFunc(terminator)) recorder = httptest.NewRecorder() request, _ = http.NewRequest("GET", "/pets", nil) @@ -135,7 +141,7 @@ spec, api := petstore.NewAPI(t) spec.Spec().BasePath = "/api///" context := NewContext(spec, api, nil) - mw := newRouter(context, http.HandlerFunc(terminator)) + mw := NewRouter(context, http.HandlerFunc(terminator)) recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/api/pets", nil) @@ -148,7 +154,7 @@ spec, api := petstore.NewAPI(t) spec.Spec().BasePath = "/api/" context := NewContext(spec, api, nil) - mw := newRouter(context, http.HandlerFunc(terminator)) + mw := NewRouter(context, http.HandlerFunc(terminator)) recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/api/pets/123", nil) @@ -161,18 +167,22 @@ mw.ServeHTTP(recorder, request) assert.Equal(t, 200, recorder.Code) - ri, _ := context.RouteInfo(request) - assert.Equal(t, "abc%2Fdef", ri.Params.Get("id")) + ri, _, _ := context.RouteInfo(request) + if assert.NotNil(t, ri) { + if assert.NotNil(t, ri.Params) { + assert.Equal(t, "abc/def", ri.Params.Get("id")) + } + } } func TestRouterStruct(t *testing.T) { spec, api := petstore.NewAPI(t) router := DefaultRouter(spec, newRoutableUntypedAPI(spec, api, new(Context))) - methods := router.OtherMethods("post", "/pets/{id}") + methods := router.OtherMethods("post", "/api/pets/{id}") assert.Len(t, methods, 2) - entry, ok := router.Lookup("delete", "/pets/{id}") + entry, ok := router.Lookup("delete", "/api/pets/{id}") assert.True(t, ok) assert.NotNil(t, entry) assert.Len(t, entry.Params, 1) @@ -194,3 +204,46 @@ return builder } + +func TestPathConverter(t *testing.T) { + cases := []struct { + swagger string + denco string + }{ + {"/", "/"}, + {"/something", "/something"}, + {"/{id}", "/:id"}, + {"/{id}/something/{anotherId}", "/:id/something/:anotherId"}, + {"/{petid}", "/:petid"}, + {"/{pet_id}", "/:pet_id"}, + {"/{petId}", "/:petId"}, + {"/{pet-id}", "/:pet-id"}, + // composit parameters tests + {"/p_{pet_id}", "/p_:pet_id"}, + {"/p_{petId}.{petSubId}", "/p_:petId"}, + } + + for _, tc := range cases { + actual := pathConverter.ReplaceAllString(tc.swagger, ":$1") + assert.Equal(t, tc.denco, actual, "expected swagger path %s to match %s but got %s", tc.swagger, tc.denco, actual) + } +} + +func TestExtractCompositParameters(t *testing.T) { + // name is the composite parameter's name, value is the value of this composit parameter, pattern is the pattern to be matched + cases := []struct { + name string + value string + pattern string + names []string + values []string + }{ + {name: "fragment", value: "gie", pattern: "e", names: []string{"fragment"}, values: []string{"gi"}}, + {name: "fragment", value: "t.simpson", pattern: ".{subfragment}", names: []string{"fragment", "subfragment"}, values: []string{"t", "simpson"}}, + } + for _, tc := range cases { + names, values := decodeCompositParams(tc.name, tc.value, tc.pattern, nil, nil) + assert.EqualValues(t, tc.names, names) + assert.EqualValues(t, tc.values, values) + } +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/security.go golang-github-go-openapi-runtime-0.15.0/middleware/security.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/security.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/security.go 2018-06-28 22:01:56.000000000 +0000 @@ -18,16 +18,21 @@ func newSecureAPI(ctx *Context, next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - route, _ := ctx.RouteInfo(r) - if len(route.Authenticators) == 0 { + route, rCtx, _ := ctx.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + if route != nil && !route.NeedsAuth() { next.ServeHTTP(rw, r) return } - if _, err := ctx.Authorize(r, route); err != nil { + _, rCtx, err := ctx.Authorize(r, route) + if err != nil { ctx.Respond(rw, r, route.Produces, route, err) return } + r = rCtx next.ServeHTTP(rw, r) }) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/security_test.go golang-github-go-openapi-runtime-0.15.0/middleware/security_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/security_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/security_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -30,27 +30,27 @@ mw := newSecureAPI(context, http.HandlerFunc(terminator)) recorder := httptest.NewRecorder() - request, _ := http.NewRequest("GET", "/pets", nil) + request, _ := http.NewRequest("GET", "/api/pets", nil) mw.ServeHTTP(recorder, request) assert.Equal(t, 401, recorder.Code) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("GET", "/pets", nil) + request, _ = http.NewRequest("GET", "/api/pets", nil) request.SetBasicAuth("admin", "wrong") mw.ServeHTTP(recorder, request) assert.Equal(t, 401, recorder.Code) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("GET", "/pets", nil) + request, _ = http.NewRequest("GET", "/api/pets", nil) request.SetBasicAuth("admin", "admin") mw.ServeHTTP(recorder, request) assert.Equal(t, 200, recorder.Code) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("GET", "/pets/1", nil) + request, _ = http.NewRequest("GET", "//apipets/1", nil) mw.ServeHTTP(recorder, request) assert.Equal(t, 200, recorder.Code) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/spec.go golang-github-go-openapi-runtime-0.15.0/middleware/spec.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/spec.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/spec.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,44 +15,29 @@ package middleware import ( - "encoding/json" "net/http" - - "github.com/go-openapi/spec" + "path" ) -func specMiddleware(ctx *Context, next http.Handler) http.Handler { - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/swagger.json" { - rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(http.StatusOK) - rw.Write(ctx.spec.Raw()) - return - } - if next == nil { - ctx.NotFound(rw, r) - return - } - next.ServeHTTP(rw, r) - }) -} - // Spec creates a middleware to serve a swagger spec. // This allows for altering the spec before starting the http listener. -// This can be useful +// This can be useful if you want to serve the swagger spec from another path than /swagger.json // -func Spec(basePath string, swsp *spec.Swagger, next http.Handler) http.Handler { - if basePath == "/" { - basePath = "" +func Spec(basePath string, b []byte, next http.Handler) http.Handler { + if basePath == "" { + basePath = "/" } + pth := path.Join(basePath, "swagger.json") + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path == basePath+"/swagger.json" { + if r.URL.Path == pth { rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusOK) - dec := json.NewEncoder(rw) - dec.Encode(swsp) + //#nosec + rw.Write(b) return } + if next == nil { rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusNotFound) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/spec_test.go golang-github-go-openapi-runtime-0.15.0/middleware/spec_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/spec_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/spec_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -28,7 +28,7 @@ spec, api := petstore.NewAPI(t) ctx := NewContext(spec, api, nil) - handler := specMiddleware(ctx, nil) + handler := Spec("", ctx.spec.Raw(), nil) // serves spec request, _ := http.NewRequest("GET", "/swagger.json", nil) request.Header.Add(runtime.HeaderContentType, runtime.JSONMime) @@ -44,7 +44,7 @@ assert.Equal(t, 404, recorder.Code) // forwards to next handler for other url - handler = specMiddleware(ctx, http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + handler = Spec("", ctx.spec.Raw(), http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(http.StatusOK) })) request, _ = http.NewRequest("GET", "/api/pets", nil) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/untyped/api.go golang-github-go-openapi-runtime-0.15.0/middleware/untyped/api.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/untyped/api.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/untyped/api.go 2018-06-28 22:01:56.000000000 +0000 @@ -34,23 +34,18 @@ if spec != nil && spec.Spec() != nil { an = analysis.New(spec.Spec()) } - return &API{ - spec: spec, - analyzer: an, - DefaultProduces: runtime.JSONMime, - DefaultConsumes: runtime.JSONMime, - consumers: map[string]runtime.Consumer{ - runtime.JSONMime: runtime.JSONConsumer(), - }, - producers: map[string]runtime.Producer{ - runtime.JSONMime: runtime.JSONProducer(), - }, + api := &API{ + spec: spec, + analyzer: an, + consumers: make(map[string]runtime.Consumer, 10), + producers: make(map[string]runtime.Producer, 10), authenticators: make(map[string]runtime.Authenticator), operations: make(map[string]map[string]runtime.OperationHandler), ServeError: errors.ServeError, Models: make(map[string]func() interface{}), formats: strfmt.NewFormats(), } + return api.WithJSONDefaults() } // API represents an untyped mux for a swagger spec @@ -62,12 +57,31 @@ consumers map[string]runtime.Consumer producers map[string]runtime.Producer authenticators map[string]runtime.Authenticator + authorizer runtime.Authorizer operations map[string]map[string]runtime.OperationHandler ServeError func(http.ResponseWriter, *http.Request, error) Models map[string]func() interface{} formats strfmt.Registry } +// WithJSONDefaults loads the json defaults for this api +func (d *API) WithJSONDefaults() *API { + d.DefaultConsumes = runtime.JSONMime + d.DefaultProduces = runtime.JSONMime + d.consumers[runtime.JSONMime] = runtime.JSONConsumer() + d.producers[runtime.JSONMime] = runtime.JSONProducer() + return d +} + +// WithoutJSONDefaults clears the json defaults for this api +func (d *API) WithoutJSONDefaults() *API { + d.DefaultConsumes = "" + d.DefaultProduces = "" + delete(d.consumers, runtime.JSONMime) + delete(d.producers, runtime.JSONMime) + return d +} + // Formats returns the registered string formats func (d *API) Formats() strfmt.Registry { if d.formats == nil { @@ -92,10 +106,15 @@ d.authenticators[scheme] = handler } +// RegisterAuthorizer registers an authorizer handler in this api +func (d *API) RegisterAuthorizer(handler runtime.Authorizer) { + d.authorizer = handler +} + // RegisterConsumer registers a consumer for a media type. func (d *API) RegisterConsumer(mediaType string, handler runtime.Consumer) { if d.consumers == nil { - d.consumers = map[string]runtime.Consumer{runtime.JSONMime: runtime.JSONConsumer()} + d.consumers = make(map[string]runtime.Consumer, 10) } d.consumers[strings.ToLower(mediaType)] = handler } @@ -103,7 +122,7 @@ // RegisterProducer registers a producer for a media type func (d *API) RegisterProducer(mediaType string, handler runtime.Producer) { if d.producers == nil { - d.producers = map[string]runtime.Producer{runtime.JSONMime: runtime.JSONProducer()} + d.producers = make(map[string]runtime.Producer, 10) } d.producers[strings.ToLower(mediaType)] = handler } @@ -111,7 +130,7 @@ // RegisterOperation registers an operation handler for an operation name func (d *API) RegisterOperation(method, path string, handler runtime.OperationHandler) { if d.operations == nil { - d.operations = make(map[string]map[string]runtime.OperationHandler) + d.operations = make(map[string]map[string]runtime.OperationHandler, 30) } um := strings.ToUpper(method) if b, ok := d.operations[um]; !ok || b == nil { @@ -165,6 +184,11 @@ return result } +// AuthorizersFor returns the registered authorizer +func (d *API) Authorizer() runtime.Authorizer { + return d.authorizer +} + // Validate validates this API for any missing items func (d *API) Validate() error { return d.validate() @@ -205,7 +229,7 @@ if err := d.verify("produces", produces, d.analyzer.RequiredProduces()); err != nil { return err } - if err := d.verify("operation", operations, d.analyzer.OperationIDs()); err != nil { + if err := d.verify("operation", operations, d.analyzer.OperationMethodPaths()); err != nil { return err } @@ -213,7 +237,6 @@ if err := d.verify("auth scheme", authenticators, requiredAuths); err != nil { return err } - fmt.Printf("comparing %s with %s\n", strings.Join(definedAuths, ","), strings.Join(requiredAuths, ",")) if err := d.verify("security definitions", definedAuths, requiredAuths); err != nil { return err } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/untyped/api_test.go golang-github-go-openapi-runtime-0.15.0/middleware/untyped/api_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/untyped/api_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/untyped/api_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -16,6 +16,7 @@ import ( "io" + "net/http" "sort" "testing" @@ -31,6 +32,10 @@ return runtime.AuthenticatorFunc(func(_ interface{}) (bool, interface{}, error) { return false, nil, nil }) } +func stubAuthorizer() runtime.Authorizer { + return runtime.AuthorizerFunc(func(_ *http.Request, _ interface{}) error { return nil }) +} + type stubConsumer struct { } @@ -57,13 +62,15 @@ } func TestUntypedAPIRegistrations(t *testing.T) { - api := NewAPI(new(loads.Document)) + api := NewAPI(new(loads.Document)).WithJSONDefaults() api.RegisterConsumer("application/yada", new(stubConsumer)) api.RegisterProducer("application/yada-2", new(stubProducer)) api.RegisterOperation("get", "/{someId}", new(stubOperationHandler)) api.RegisterAuth("basic", stubAutenticator()) + api.RegisterAuthorizer(stubAuthorizer()) + assert.NotNil(t, api.authorizer) assert.NotEmpty(t, api.authenticators) _, ok := api.authenticators["basic"] @@ -79,6 +86,9 @@ _, ok = api.operations["GET"]["/{someId}"] assert.True(t, ok) + authorizer := api.Authorizer() + assert.NotNil(t, authorizer) + h, ok := api.OperationHandlerFor("get", "/{someId}") assert.True(t, ok) assert.NotNil(t, h) @@ -117,7 +127,6 @@ "security": [ {"basic":[]} ], - "operationId": "someOperation", "parameters": [ { "name": "skip", @@ -162,7 +171,6 @@ "security": [ {"basic":[]} ], - "operationId": "someOperation", "parameters": [ { "name": "skip", diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/untyped_request_test.go golang-github-go-openapi-runtime-0.15.0/middleware/untyped_request_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/untyped_request_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/untyped_request_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -59,8 +59,8 @@ part, err := writer.CreateFormFile("file", "plain-jane.txt") assert.NoError(t, err) - part.Write([]byte("the file contents")) - writer.WriteField("name", "the-name") + _, _ = part.Write([]byte("the file contents")) + _ = writer.WriteField("name", "the-name") assert.NoError(t, writer.Close()) urlStr := "http://localhost:8002/hello" @@ -95,8 +95,8 @@ part, err = writer.CreateFormFile("bad-name", "plain-jane.txt") assert.NoError(t, err) - part.Write([]byte("the file contents")) - writer.WriteField("name", "the-name") + _, _ = part.Write([]byte("the file contents")) + _ = writer.WriteField("name", "the-name") assert.NoError(t, writer.Close()) req, _ = http.NewRequest("POST", urlStr, body) req.Header.Set("Content-Type", writer.FormDataContentType()) @@ -106,7 +106,7 @@ req, _ = http.NewRequest("POST", urlStr, body) req.Header.Set("Content-Type", writer.FormDataContentType()) - req.MultipartReader() + _, _ = req.MultipartReader() data = make(map[string]interface{}) assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data)) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/validation.go golang-github-go-openapi-runtime-0.15.0/middleware/validation.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/validation.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/validation.go 2018-06-28 22:01:56.000000000 +0000 @@ -17,28 +17,13 @@ import ( "mime" "net/http" + "strings" "github.com/go-openapi/errors" "github.com/go-openapi/runtime" "github.com/go-openapi/swag" ) -// NewValidation starts a new validation middleware -func newValidation(ctx *Context, next http.Handler) http.Handler { - - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - matched, _ := ctx.RouteInfo(r) - _, result := ctx.BindAndValidate(r, matched) - - if result != nil { - ctx.Respond(rw, r, matched.Produces, matched, result) - return - } - - next.ServeHTTP(rw, r) - }) -} - type validation struct { context *Context result []error @@ -47,17 +32,9 @@ bound map[string]interface{} } -type untypedBinder map[string]interface{} - -func (ub untypedBinder) BindRequest(r *http.Request, route *MatchedRoute, consumer runtime.Consumer) error { - if err := route.Binder.Bind(r, route.Params, consumer, ub); err != nil { - return err - } - return nil -} - // ContentType validates the content type of a request func validateContentType(allowed []string, actual string) error { + debugLog("validating content type for %q against [%s]", actual, strings.Join(allowed, ", ")) if len(allowed) == 0 { return nil } @@ -68,10 +45,18 @@ if swag.ContainsStringsCI(allowed, mt) { return nil } + if swag.ContainsStringsCI(allowed, "*/*") { + return nil + } + parts := strings.Split(actual, "/") + if len(parts) == 2 && swag.ContainsStringsCI(allowed, parts[0]+"/*") { + return nil + } return errors.InvalidContentType(actual, allowed) } func validateRequest(ctx *Context, request *http.Request, route *MatchedRoute) *validation { + debugLog("validating request %s %s", request.Method, request.URL.EscapedPath()) validate := &validation{ context: ctx, request: request, @@ -91,6 +76,7 @@ } func (v *validation) parameters() { + debugLog("validating request parameters for %s %s", v.request.Method, v.request.URL.EscapedPath()) if result := v.route.Binder.Bind(v.request, v.route.Params, v.route.Consumer, v.bound); result != nil { if result.Error() == "validation failure list" { for _, e := range result.(*errors.Validation).Value.([]interface{}) { @@ -104,10 +90,14 @@ func (v *validation) contentType() { if len(v.result) == 0 && runtime.HasBody(v.request) { - ct, _, err := v.context.ContentType(v.request) + debugLog("validating body content type for %s %s", v.request.Method, v.request.URL.EscapedPath()) + ct, _, req, err := v.context.ContentType(v.request) if err != nil { v.result = append(v.result, err) + } else { + v.request = req } + if len(v.result) == 0 { if err := validateContentType(v.route.Consumes, ct); err != nil { v.result = append(v.result, err) @@ -125,7 +115,8 @@ } func (v *validation) responseFormat() { - if str := v.context.ResponseFormat(v.request, v.route.Produces); str == "" && runtime.HasBody(v.request) { + if str, rCtx := v.context.ResponseFormat(v.request, v.route.Produces); str == "" && runtime.HasBody(v.request) { + v.request = rCtx v.result = append(v.result, errors.InvalidResponseFormat(v.request.Header.Get(runtime.HeaderAccept), v.route.Produces)) } } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/validation_test.go golang-github-go-openapi-runtime-0.15.0/middleware/validation_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/middleware/validation_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/middleware/validation_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -26,49 +26,72 @@ "github.com/stretchr/testify/assert" ) +func newTestValidation(ctx *Context, next http.Handler) http.Handler { + + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + matched, rCtx, _ := ctx.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + if matched == nil { + ctx.NotFound(rw, r) + return + } + _, r, result := ctx.BindAndValidate(r, matched) + + if result != nil { + ctx.Respond(rw, r, matched.Produces, matched, result) + return + } + + next.ServeHTTP(rw, r) + }) +} + func TestContentTypeValidation(t *testing.T) { spec, api := petstore.NewAPI(t) context := NewContext(spec, api, nil) context.router = DefaultRouter(spec, context.api) - mw := newValidation(context, http.HandlerFunc(terminator)) + mw := newTestValidation(context, http.HandlerFunc(terminator)) recorder := httptest.NewRecorder() - request, _ := http.NewRequest("GET", "/pets", nil) + request, _ := http.NewRequest("GET", "/api/pets", nil) request.Header.Add("Accept", "*/*") mw.ServeHTTP(recorder, request) - assert.Equal(t, 200, recorder.Code) + assert.Equal(t, http.StatusOK, recorder.Code) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("POST", "/pets", nil) + request, _ = http.NewRequest("POST", "/api/pets", nil) request.Header.Add("content-type", "application(") + request.Header.Add("Accept", "application/json") request.ContentLength = 1 mw.ServeHTTP(recorder, request) - assert.Equal(t, 400, recorder.Code) + assert.Equal(t, http.StatusBadRequest, recorder.Code) assert.Equal(t, "application/json", recorder.Header().Get("content-type")) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("POST", "/pets", nil) + request, _ = http.NewRequest("POST", "/api/pets", nil) request.Header.Add("Accept", "application/json") request.Header.Add("content-type", "text/html") request.ContentLength = 1 mw.ServeHTTP(recorder, request) - assert.Equal(t, 415, recorder.Code) + assert.Equal(t, http.StatusUnsupportedMediaType, recorder.Code) assert.Equal(t, "application/json", recorder.Header().Get("content-type")) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("POST", "/pets", nil) + request, _ = http.NewRequest("POST", "/api/pets", nil) request.Header.Add("Accept", "application/json") request.Header.Add("content-type", "text/html") request.TransferEncoding = []string{"chunked"} mw.ServeHTTP(recorder, request) - assert.Equal(t, 415, recorder.Code) + assert.Equal(t, http.StatusUnsupportedMediaType, recorder.Code) assert.Equal(t, "application/json", recorder.Header().Get("content-type")) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("POST", "/pets", nil) + request, _ = http.NewRequest("POST", "/api/pets", nil) request.Header.Add("Accept", "application/json") request.Header.Add("content-type", "text/html") @@ -81,10 +104,10 @@ spec, api := petstore.NewAPI(t) context := NewContext(spec, api, nil) context.router = DefaultRouter(spec, context.api) - mw := newValidation(context, http.HandlerFunc(terminator)) + mw := newTestValidation(context, http.HandlerFunc(terminator)) recorder := httptest.NewRecorder() - request, _ := http.NewRequest("POST", "/pets", bytes.NewBuffer([]byte(`name: Dog`))) + request, _ := http.NewRequest("POST", "/api/pets", bytes.NewBuffer([]byte(`name: Dog`))) request.Header.Set(runtime.HeaderContentType, "application/x-yaml") request.Header.Set(runtime.HeaderAccept, "application/x-yaml") @@ -92,7 +115,7 @@ assert.Equal(t, 200, recorder.Code, recorder.Body.String()) recorder = httptest.NewRecorder() - request, _ = http.NewRequest("POST", "/pets", bytes.NewBuffer([]byte(`name: Dog`))) + request, _ = http.NewRequest("POST", "/api/pets", bytes.NewBuffer([]byte(`name: Dog`))) request.Header.Set(runtime.HeaderContentType, "application/x-yaml") request.Header.Set(runtime.HeaderAccept, "application/sml") @@ -114,6 +137,8 @@ {"text/html; charset=utf-8", []string{"application/json"}, errors.InvalidContentType("text/html; charset=utf-8", []string{"application/json"})}, {"application(", []string{"application/json"}, errors.InvalidContentType("application(", []string{"application/json"})}, {"application/json;char*", []string{"application/json"}, errors.InvalidContentType("application/json;char*", []string{"application/json"})}, + {"application/octet-stream", []string{"image/jpeg", "application/*"}, nil}, + {"image/png", []string{"*/*", "application/json"}, nil}, } for _, v := range data { diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.pullapprove.yml golang-github-go-openapi-runtime-0.15.0/.pullapprove.yml --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.pullapprove.yml 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/.pullapprove.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -approve_by_comment: true -approve_regex: '^(:shipit:|:\+1:|\+1|LGTM|lgtm|Approved)' -reject_regex: ^[Rr]ejected -reset_on_push: false -reviewers: - members: - - casualjim - - chancez - - frapposelli - - vburenin - - pytlesk4 - name: pullapprove - required: 1 diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/README.md golang-github-go-openapi-runtime-0.15.0/README.md --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/README.md 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/README.md 2018-06-28 22:01:56.000000000 +0000 @@ -1,4 +1,4 @@ -# runtime [![Build Status](https://ci.vmware.run/api/badges/go-openapi/runtime/status.svg)](https://ci.vmware.run/go-openapi/runtime) [![Coverage](https://coverage.vmware.run/badges/go-openapi/runtime/coverage.svg)](https://coverage.vmware.run/go-openapi/runtime) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) +# runtime [![Build Status](https://travis-ci.org/go-openapi/runtime.svg?branch=client-context)](https://travis-ci.org/go-openapi/runtime) [![codecov](https://codecov.io/gh/go-openapi/runtime/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/runtime) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) [![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/runtime/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/runtime?status.svg)](http://godoc.org/github.com/go-openapi/runtime) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/apikey_auth_test.go golang-github-go-openapi-runtime-0.15.0/security/apikey_auth_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/apikey_auth_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/security/apikey_auth_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,6 +15,7 @@ package security import ( + "context" "net/http" "testing" @@ -29,6 +30,13 @@ return nil, errors.Unauthenticated("token") }) +var tokenAuthCtx = TokenAuthenticationCtx(func(ctx context.Context, token string) (context.Context, interface{}, error) { + if token == "token123" { + return context.WithValue(ctx, extra, extraWisdom), "admin", nil + } + return context.WithValue(ctx, reason, expReason), nil, errors.Unauthenticated("token") +}) + func TestInvalidApiKeyAuthInitialization(t *testing.T) { assert.Panics(t, func() { APIKeyAuth("api_key", "qery", tokenAuth) }) } @@ -92,3 +100,88 @@ assert.Equal(t, nil, usr) assert.NoError(t, err) } + +func TestInvalidApiKeyAuthInitializationCtx(t *testing.T) { + assert.Panics(t, func() { APIKeyAuthCtx("api_key", "qery", tokenAuthCtx) }) +} + +func TestValidApiKeyAuthCtx(t *testing.T) { + ta := APIKeyAuthCtx("api_key", "query", tokenAuthCtx) + ta2 := APIKeyAuthCtx("X-API-KEY", "header", tokenAuthCtx) + + req1, _ := http.NewRequest("GET", "/blah?api_key=token123", nil) + req1 = req1.WithContext(context.WithValue(req1.Context(), original, wisdom)) + ok, usr, err := ta.Authenticate(req1) + assert.True(t, ok) + assert.Equal(t, "admin", usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req1.Context().Value(original)) + assert.Equal(t, extraWisdom, req1.Context().Value(extra)) + assert.Nil(t, req1.Context().Value(reason)) + + req2, _ := http.NewRequest("GET", "/blah", nil) + req2 = req2.WithContext(context.WithValue(req2.Context(), original, wisdom)) + req2.Header.Set("X-API-KEY", "token123") + + ok, usr, err = ta2.Authenticate(req2) + assert.True(t, ok) + assert.Equal(t, "admin", usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req2.Context().Value(original)) + assert.Equal(t, extraWisdom, req2.Context().Value(extra)) + assert.Nil(t, req2.Context().Value(reason)) +} + +func TestInvalidApiKeyAuthCtx(t *testing.T) { + ta := APIKeyAuthCtx("api_key", "query", tokenAuthCtx) + ta2 := APIKeyAuthCtx("X-API-KEY", "header", tokenAuthCtx) + + req1, _ := http.NewRequest("GET", "/blah?api_key=token124", nil) + req1 = req1.WithContext(context.WithValue(req1.Context(), original, wisdom)) + ok, usr, err := ta.Authenticate(req1) + assert.True(t, ok) + assert.Equal(t, nil, usr) + assert.Error(t, err) + assert.Equal(t, wisdom, req1.Context().Value(original)) + assert.Equal(t, expReason, req1.Context().Value(reason)) + assert.Nil(t, req1.Context().Value(extra)) + + req2, _ := http.NewRequest("GET", "/blah", nil) + req2 = req2.WithContext(context.WithValue(req2.Context(), original, wisdom)) + req2.Header.Set("X-API-KEY", "token124") + + ok, usr, err = ta2.Authenticate(req2) + assert.True(t, ok) + assert.Equal(t, nil, usr) + assert.Error(t, err) + assert.Equal(t, wisdom, req2.Context().Value(original)) + assert.Equal(t, expReason, req2.Context().Value(reason)) + assert.Nil(t, req2.Context().Value(extra)) +} + +func TestMissingApiKeyAuthCtx(t *testing.T) { + ta := APIKeyAuthCtx("api_key", "query", tokenAuthCtx) + ta2 := APIKeyAuthCtx("X-API-KEY", "header", tokenAuthCtx) + + req1, _ := http.NewRequest("GET", "/blah", nil) + req1 = req1.WithContext(context.WithValue(req1.Context(), original, wisdom)) + req1.Header.Set("X-API-KEY", "token123") + + ok, usr, err := ta.Authenticate(req1) + assert.False(t, ok) + assert.Equal(t, nil, usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req1.Context().Value(original)) + assert.Nil(t, req1.Context().Value(reason)) + assert.Nil(t, req1.Context().Value(extra)) + + req2, _ := http.NewRequest("GET", "/blah?api_key=token123", nil) + req2 = req2.WithContext(context.WithValue(req2.Context(), original, wisdom)) + ok, usr, err = ta2.Authenticate(req2) + assert.False(t, ok) + assert.Equal(t, nil, usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req2.Context().Value(original)) + assert.Nil(t, req2.Context().Value(reason)) + assert.Nil(t, req2.Context().Value(extra)) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/authenticator.go golang-github-go-openapi-runtime-0.15.0/security/authenticator.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/authenticator.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/security/authenticator.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,6 +15,7 @@ package security import ( + "context" "net/http" "strings" @@ -22,8 +23,13 @@ "github.com/go-openapi/runtime" ) -// httpAuthenticator is a function that authenticates a HTTP request -func httpAuthenticator(handler func(*http.Request) (bool, interface{}, error)) runtime.Authenticator { +const ( + query = "query" + header = "header" +) + +// HttpAuthenticator is a function that authenticates a HTTP request +func HttpAuthenticator(handler func(*http.Request) (bool, interface{}, error)) runtime.Authenticator { return runtime.AuthenticatorFunc(func(params interface{}) (bool, interface{}, error) { if request, ok := params.(*http.Request); ok { return handler(request) @@ -35,7 +41,8 @@ }) } -func scopedAuthenticator(handler func(*ScopedAuthRequest) (bool, interface{}, error)) runtime.Authenticator { +// ScopedAuthenticator is a function that authenticates a HTTP request against a list of valid scopes +func ScopedAuthenticator(handler func(*ScopedAuthRequest) (bool, interface{}, error)) runtime.Authenticator { return runtime.AuthenticatorFunc(func(params interface{}) (bool, interface{}, error) { if request, ok := params.(*ScopedAuthRequest); ok { return handler(request) @@ -47,15 +54,24 @@ // UserPassAuthentication authentication function type UserPassAuthentication func(string, string) (interface{}, error) +// UserPassAuthenticationCtx authentication function with context.Context +type UserPassAuthenticationCtx func(context.Context, string, string) (context.Context, interface{}, error) + // TokenAuthentication authentication function type TokenAuthentication func(string) (interface{}, error) +// TokenAuthenticationCtx authentication function with context.Context +type TokenAuthenticationCtx func(context.Context, string) (context.Context, interface{}, error) + // ScopedTokenAuthentication authentication function type ScopedTokenAuthentication func(string, []string) (interface{}, error) +// ScopedTokenAuthenticationCtx authentication function with context.Context +type ScopedTokenAuthenticationCtx func(context.Context, string, []string) (context.Context, interface{}, error) + // BasicAuth creates a basic auth authenticator with the provided authentication function func BasicAuth(authenticate UserPassAuthentication) runtime.Authenticator { - return httpAuthenticator(func(r *http.Request) (bool, interface{}, error) { + return HttpAuthenticator(func(r *http.Request) (bool, interface{}, error) { if usr, pass, ok := r.BasicAuth(); ok { p, err := authenticate(usr, pass) return true, p, err @@ -65,24 +81,37 @@ }) } +// BasicAuthCtx creates a basic auth authenticator with the provided authentication function with support for context.Context +func BasicAuthCtx(authenticate UserPassAuthenticationCtx) runtime.Authenticator { + return HttpAuthenticator(func(r *http.Request) (bool, interface{}, error) { + if usr, pass, ok := r.BasicAuth(); ok { + ctx, p, err := authenticate(r.Context(), usr, pass) + *r = *r.WithContext(ctx) + return true, p, err + } + + return false, nil, nil + }) +} + // APIKeyAuth creates an authenticator that uses a token for authorization. // This token can be obtained from either a header or a query string func APIKeyAuth(name, in string, authenticate TokenAuthentication) runtime.Authenticator { inl := strings.ToLower(in) - if inl != "query" && inl != "header" { + if inl != query && inl != header { // panic because this is most likely a typo panic(errors.New(500, "api key auth: in value needs to be either \"query\" or \"header\".")) } var getToken func(*http.Request) string switch inl { - case "header": + case header: getToken = func(r *http.Request) string { return r.Header.Get(name) } - case "query": + case query: getToken = func(r *http.Request) string { return r.URL.Query().Get(name) } } - return httpAuthenticator(func(r *http.Request) (bool, interface{}, error) { + return HttpAuthenticator(func(r *http.Request) (bool, interface{}, error) { token := getToken(r) if token == "" { return false, nil, nil @@ -93,6 +122,35 @@ }) } +// APIKeyAuthCtx creates an authenticator that uses a token for authorization with support for context.Context. +// This token can be obtained from either a header or a query string +func APIKeyAuthCtx(name, in string, authenticate TokenAuthenticationCtx) runtime.Authenticator { + inl := strings.ToLower(in) + if inl != query && inl != header { + // panic because this is most likely a typo + panic(errors.New(500, "api key auth: in value needs to be either \"query\" or \"header\".")) + } + + var getToken func(*http.Request) string + switch inl { + case header: + getToken = func(r *http.Request) string { return r.Header.Get(name) } + case query: + getToken = func(r *http.Request) string { return r.URL.Query().Get(name) } + } + + return HttpAuthenticator(func(r *http.Request) (bool, interface{}, error) { + token := getToken(r) + if token == "" { + return false, nil, nil + } + + ctx, p, err := authenticate(r.Context(), token) + *r = *r.WithContext(ctx) + return true, p, err + }) +} + // ScopedAuthRequest contains both a http request and the required scopes for a particular operation type ScopedAuthRequest struct { Request *http.Request @@ -102,7 +160,7 @@ // BearerAuth for use with oauth2 flows func BearerAuth(name string, authenticate ScopedTokenAuthentication) runtime.Authenticator { const prefix = "Bearer " - return scopedAuthenticator(func(r *ScopedAuthRequest) (bool, interface{}, error) { + return ScopedAuthenticator(func(r *ScopedAuthRequest) (bool, interface{}, error) { var token string hdr := r.Request.Header.Get("Authorization") if strings.HasPrefix(hdr, prefix) { @@ -112,6 +170,7 @@ qs := r.Request.URL.Query() token = qs.Get("access_token") } + //#nosec ct, _, _ := runtime.ContentType(r.Request.Header) if token == "" && (ct == "application/x-www-form-urlencoded" || ct == "multipart/form-data") { token = r.Request.FormValue("access_token") @@ -125,3 +184,32 @@ return true, p, err }) } + +// BearerAuthCtx for use with oauth2 flows with support for context.Context. +func BearerAuthCtx(name string, authenticate ScopedTokenAuthenticationCtx) runtime.Authenticator { + const prefix = "Bearer " + return ScopedAuthenticator(func(r *ScopedAuthRequest) (bool, interface{}, error) { + var token string + hdr := r.Request.Header.Get("Authorization") + if strings.HasPrefix(hdr, prefix) { + token = strings.TrimPrefix(hdr, prefix) + } + if token == "" { + qs := r.Request.URL.Query() + token = qs.Get("access_token") + } + //#nosec + ct, _, _ := runtime.ContentType(r.Request.Header) + if token == "" && (ct == "application/x-www-form-urlencoded" || ct == "multipart/form-data") { + token = r.Request.FormValue("access_token") + } + + if token == "" { + return false, nil, nil + } + + ctx, p, err := authenticate(r.Request.Context(), token, r.RequiredScopes) + *r.Request = *r.Request.WithContext(ctx) + return true, p, err + }) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/authorizer.go golang-github-go-openapi-runtime-0.15.0/security/authorizer.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/authorizer.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/security/authorizer.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,27 @@ +// Copyright 2015 go-swagger maintainers +// +// 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 +// +// http://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 security + +import ( + "net/http" + + "github.com/go-openapi/runtime" +) + +// Authorized provides a default implementation of the Authorizer interface where all +// requests are authorized (successful) +func Authorized() runtime.Authorizer { + return runtime.AuthorizerFunc(func(_ *http.Request, _ interface{}) error { return nil }) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/authorizer_test.go golang-github-go-openapi-runtime-0.15.0/security/authorizer_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/authorizer_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/security/authorizer_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,28 @@ +// Copyright 2015 go-swagger maintainers +// +// 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 +// +// http://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 security + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAuthorized(t *testing.T) { + authorizer := Authorized() + + err := authorizer.Authorize(nil, nil) + assert.NoError(t, err) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/basic_auth_test.go golang-github-go-openapi-runtime-0.15.0/security/basic_auth_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/basic_auth_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/security/basic_auth_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,6 +15,7 @@ package security import ( + "context" "net/http" "testing" @@ -69,6 +70,84 @@ ok, usr, err := ba.Authenticate("token") + assert.NoError(t, err) + assert.False(t, ok) + assert.Nil(t, usr) +} + +type secTestKey uint8 + +const ( + original secTestKey = iota + extra + reason +) + +const ( + wisdom = "The man who is swimming against the stream knows the strength of it." + extraWisdom = "Our greatest glory is not in never falling, but in rising every time we fall." + expReason = "I like the dreams of the future better than the history of the past." +) + +var basicAuthHandlerCtx = UserPassAuthenticationCtx(func(ctx context.Context, user, pass string) (context.Context, interface{}, error) { + if user == "admin" && pass == "123456" { + return context.WithValue(ctx, extra, extraWisdom), "admin", nil + } + return context.WithValue(ctx, reason, expReason), "", errors.Unauthenticated("basic") +}) + +func TestValidBasicAuthCtx(t *testing.T) { + ba := BasicAuthCtx(basicAuthHandlerCtx) + + req, _ := http.NewRequest("GET", "/blah", nil) + req = req.WithContext(context.WithValue(req.Context(), original, wisdom)) + req.SetBasicAuth("admin", "123456") + ok, usr, err := ba.Authenticate(req) + + assert.NoError(t, err) + assert.True(t, ok) + assert.Equal(t, "admin", usr) + assert.Equal(t, wisdom, req.Context().Value(original)) + assert.Equal(t, extraWisdom, req.Context().Value(extra)) + assert.Nil(t, req.Context().Value(reason)) +} + +func TestInvalidBasicAuthCtx(t *testing.T) { + ba := BasicAuthCtx(basicAuthHandlerCtx) + + req, _ := http.NewRequest("GET", "/blah", nil) + req = req.WithContext(context.WithValue(req.Context(), original, wisdom)) + req.SetBasicAuth("admin", "admin") + ok, usr, err := ba.Authenticate(req) + + assert.Error(t, err) + assert.True(t, ok) + assert.Equal(t, "", usr) + assert.Equal(t, wisdom, req.Context().Value(original)) + assert.Nil(t, req.Context().Value(extra)) + assert.Equal(t, expReason, req.Context().Value(reason)) +} + +func TestMissingbasicAuthCtx(t *testing.T) { + ba := BasicAuthCtx(basicAuthHandlerCtx) + + req, _ := http.NewRequest("GET", "/blah", nil) + req = req.WithContext(context.WithValue(req.Context(), original, wisdom)) + ok, usr, err := ba.Authenticate(req) + assert.NoError(t, err) + assert.False(t, ok) + assert.Equal(t, nil, usr) + + assert.Equal(t, wisdom, req.Context().Value(original)) + assert.Nil(t, req.Context().Value(extra)) + assert.Nil(t, req.Context().Value(reason)) +} + +func TestNoRequestBasicAuthCtx(t *testing.T) { + ba := BasicAuthCtx(basicAuthHandlerCtx) + + ok, usr, err := ba.Authenticate("token") + assert.NoError(t, err) assert.False(t, ok) assert.Nil(t, usr) diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/bearer_auth_test.go golang-github-go-openapi-runtime-0.15.0/security/bearer_auth_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/security/bearer_auth_test.go 2017-02-21 07:41:42.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/security/bearer_auth_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -2,6 +2,7 @@ import ( "bytes" + "context" "mime/multipart" "net/http" "net/url" @@ -49,7 +50,7 @@ mpbody := bytes.NewBuffer(nil) writer := multipart.NewWriter(mpbody) - writer.WriteField("access_token", "token123") + _ = writer.WriteField("access_token", "token123") writer.Close() req4, _ := http.NewRequest("POST", "/blah", mpbody) req4.Header.Set("Content-Type", writer.FormDataContentType()) @@ -90,7 +91,7 @@ mpbody := bytes.NewBuffer(nil) writer := multipart.NewWriter(mpbody) - writer.WriteField("access_token", "token124") + _ = writer.WriteField("access_token", "token124") writer.Close() req4, _ := http.NewRequest("POST", "/blah", mpbody) req4.Header.Set("Content-Type", writer.FormDataContentType()) @@ -131,7 +132,7 @@ mpbody := bytes.NewBuffer(nil) writer := multipart.NewWriter(mpbody) - writer.WriteField("access_toke", "token123") + _ = writer.WriteField("access_toke", "token123") writer.Close() req4, _ := http.NewRequest("POST", "/blah", mpbody) req4.Header.Set("Content-Type", writer.FormDataContentType()) @@ -141,3 +142,178 @@ assert.Equal(t, nil, usr) assert.NoError(t, err) } + +var bearerAuthCtx = ScopedTokenAuthenticationCtx(func(ctx context.Context, token string, requiredScopes []string) (context.Context, interface{}, error) { + if token == "token123" { + return context.WithValue(ctx, extra, extraWisdom), "admin", nil + } + return context.WithValue(ctx, reason, expReason), nil, errors.Unauthenticated("bearer") +}) + +func TestValidBearerAuthCtx(t *testing.T) { + ba := BearerAuthCtx("owners_auth", bearerAuthCtx) + + req1, _ := http.NewRequest("GET", "/blah?access_token=token123", nil) + req1 = req1.WithContext(context.WithValue(req1.Context(), original, wisdom)) + ok, usr, err := ba.Authenticate(&ScopedAuthRequest{Request: req1}) + assert.True(t, ok) + assert.Equal(t, "admin", usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req1.Context().Value(original)) + assert.Equal(t, extraWisdom, req1.Context().Value(extra)) + assert.Nil(t, req1.Context().Value(reason)) + + req2, _ := http.NewRequest("GET", "/blah", nil) + req2 = req2.WithContext(context.WithValue(req2.Context(), original, wisdom)) + req2.Header.Set("Authorization", "Bearer token123") + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req2}) + assert.True(t, ok) + assert.Equal(t, "admin", usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req2.Context().Value(original)) + assert.Equal(t, extraWisdom, req2.Context().Value(extra)) + assert.Nil(t, req2.Context().Value(reason)) + + body := url.Values(map[string][]string{}) + body.Set("access_token", "token123") + req3, _ := http.NewRequest("POST", "/blah", strings.NewReader(body.Encode())) + req3 = req3.WithContext(context.WithValue(req3.Context(), original, wisdom)) + req3.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req3}) + assert.True(t, ok) + assert.Equal(t, "admin", usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req3.Context().Value(original)) + assert.Equal(t, extraWisdom, req3.Context().Value(extra)) + assert.Nil(t, req3.Context().Value(reason)) + + mpbody := bytes.NewBuffer(nil) + writer := multipart.NewWriter(mpbody) + _ = writer.WriteField("access_token", "token123") + writer.Close() + req4, _ := http.NewRequest("POST", "/blah", mpbody) + req4 = req4.WithContext(context.WithValue(req4.Context(), original, wisdom)) + req4.Header.Set("Content-Type", writer.FormDataContentType()) + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req4}) + assert.True(t, ok) + assert.Equal(t, "admin", usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req4.Context().Value(original)) + assert.Equal(t, extraWisdom, req4.Context().Value(extra)) + assert.Nil(t, req4.Context().Value(reason)) +} + +func TestInvalidBearerAuthCtx(t *testing.T) { + ba := BearerAuthCtx("owners_auth", bearerAuthCtx) + + req1, _ := http.NewRequest("GET", "/blah?access_token=token124", nil) + req1 = req1.WithContext(context.WithValue(req1.Context(), original, wisdom)) + ok, usr, err := ba.Authenticate(&ScopedAuthRequest{Request: req1}) + assert.True(t, ok) + assert.Equal(t, nil, usr) + assert.Error(t, err) + assert.Equal(t, wisdom, req1.Context().Value(original)) + assert.Equal(t, expReason, req1.Context().Value(reason)) + assert.Nil(t, req1.Context().Value(extra)) + + req2, _ := http.NewRequest("GET", "/blah", nil) + req2 = req2.WithContext(context.WithValue(req2.Context(), original, wisdom)) + req2.Header.Set("Authorization", "Bearer token124") + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req2}) + assert.True(t, ok) + assert.Equal(t, nil, usr) + assert.Error(t, err) + assert.Equal(t, wisdom, req2.Context().Value(original)) + assert.Equal(t, expReason, req2.Context().Value(reason)) + assert.Nil(t, req2.Context().Value(extra)) + + body := url.Values(map[string][]string{}) + body.Set("access_token", "token124") + req3, _ := http.NewRequest("POST", "/blah", strings.NewReader(body.Encode())) + req3 = req3.WithContext(context.WithValue(req3.Context(), original, wisdom)) + req3.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req3}) + assert.True(t, ok) + assert.Equal(t, nil, usr) + assert.Error(t, err) + assert.Equal(t, wisdom, req3.Context().Value(original)) + assert.Equal(t, expReason, req3.Context().Value(reason)) + assert.Nil(t, req3.Context().Value(extra)) + + mpbody := bytes.NewBuffer(nil) + writer := multipart.NewWriter(mpbody) + _ = writer.WriteField("access_token", "token124") + writer.Close() + req4, _ := http.NewRequest("POST", "/blah", mpbody) + req4 = req4.WithContext(context.WithValue(req4.Context(), original, wisdom)) + req4.Header.Set("Content-Type", writer.FormDataContentType()) + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req4}) + assert.True(t, ok) + assert.Equal(t, nil, usr) + assert.Error(t, err) + assert.Equal(t, wisdom, req4.Context().Value(original)) + assert.Equal(t, expReason, req4.Context().Value(reason)) + assert.Nil(t, req4.Context().Value(extra)) +} + +func TestMissingBearerAuthCtx(t *testing.T) { + ba := BearerAuthCtx("owners_auth", bearerAuthCtx) + + req1, _ := http.NewRequest("GET", "/blah?access_toke=token123", nil) + req1 = req1.WithContext(context.WithValue(req1.Context(), original, wisdom)) + ok, usr, err := ba.Authenticate(&ScopedAuthRequest{Request: req1}) + assert.False(t, ok) + assert.Equal(t, nil, usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req1.Context().Value(original)) + assert.Nil(t, req1.Context().Value(reason)) + assert.Nil(t, req1.Context().Value(extra)) + + req2, _ := http.NewRequest("GET", "/blah", nil) + req2.Header.Set("Authorization", "Beare token123") + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req2}) + req2 = req2.WithContext(context.WithValue(req2.Context(), original, wisdom)) + assert.False(t, ok) + assert.Equal(t, nil, usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req2.Context().Value(original)) + assert.Nil(t, req2.Context().Value(reason)) + assert.Nil(t, req2.Context().Value(extra)) + + body := url.Values(map[string][]string{}) + body.Set("access_toke", "token123") + req3, _ := http.NewRequest("POST", "/blah", strings.NewReader(body.Encode())) + req3 = req3.WithContext(context.WithValue(req3.Context(), original, wisdom)) + req3.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req3}) + assert.False(t, ok) + assert.Equal(t, nil, usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req3.Context().Value(original)) + assert.Nil(t, req3.Context().Value(reason)) + assert.Nil(t, req3.Context().Value(extra)) + + mpbody := bytes.NewBuffer(nil) + writer := multipart.NewWriter(mpbody) + _ = writer.WriteField("access_toke", "token123") + writer.Close() + req4, _ := http.NewRequest("POST", "/blah", mpbody) + req4 = req4.WithContext(context.WithValue(req4.Context(), original, wisdom)) + req4.Header.Set("Content-Type", writer.FormDataContentType()) + + ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req4}) + assert.False(t, ok) + assert.Equal(t, nil, usr) + assert.NoError(t, err) + assert.Equal(t, wisdom, req4.Context().Value(original)) + assert.Nil(t, req4.Context().Value(reason)) + assert.Nil(t, req4.Context().Value(extra)) +} diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/statuses.go golang-github-go-openapi-runtime-0.15.0/statuses.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/statuses.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/statuses.go 2018-06-28 22:01:56.000000000 +0000 @@ -15,7 +15,7 @@ package runtime // Statuses lists the most common HTTP status codes to default message -// taken from http://status.es +// taken from https://httpstatuses.com/ var Statuses = map[int]string{ 100: "Continue", 101: "Switching Protocols", @@ -29,7 +29,7 @@ 204: "No Content", 205: "Reset Content", 206: "Partial Content", - 207: "Mutli-Status", + 207: "Multi-Status", 208: "Already Reported", 226: "IM Used", 300: "Multiple Choices", diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/text.go golang-github-go-openapi-runtime-0.15.0/text.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/text.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/text.go 2018-06-28 22:01:56.000000000 +0000 @@ -16,8 +16,11 @@ import ( "bytes" + "encoding" + "errors" + "fmt" "io" - "unsafe" + "reflect" "github.com/go-openapi/swag" ) @@ -25,28 +28,84 @@ // TextConsumer creates a new text consumer func TextConsumer() Consumer { return ConsumerFunc(func(reader io.Reader, data interface{}) error { + if reader == nil { + return errors.New("TextConsumer requires a reader") // early exit + } + buf := new(bytes.Buffer) _, err := buf.ReadFrom(reader) if err != nil { return err } b := buf.Bytes() - *(data.(*string)) = *(*string)(unsafe.Pointer(&b)) - return nil + + if tu, ok := data.(encoding.TextUnmarshaler); ok { + err := tu.UnmarshalText(b) + if err != nil { + return fmt.Errorf("text consumer: %v", err) + } + + return nil + } + + t := reflect.TypeOf(data) + if data != nil && t.Kind() == reflect.Ptr { + v := reflect.Indirect(reflect.ValueOf(data)) + if t.Elem().Kind() == reflect.String { + v.SetString(string(b)) + return nil + } + } + + return fmt.Errorf("%v (%T) is not supported by the TextConsumer, %s", + data, data, "can be resolved by supporting TextUnmarshaler interface") }) } // TextProducer creates a new text producer func TextProducer() Producer { return ProducerFunc(func(writer io.Writer, data interface{}) error { - var buf *bytes.Buffer - switch tped := data.(type) { - case *string: - buf = bytes.NewBufferString(swag.StringValue(tped)) - case string: - buf = bytes.NewBufferString(tped) + if writer == nil { + return errors.New("TextProducer requires a writer") // early exit + } + + if data == nil { + return errors.New("no data given to produce text from") + } + + if tm, ok := data.(encoding.TextMarshaler); ok { + txt, err := tm.MarshalText() + if err != nil { + return fmt.Errorf("text producer: %v", err) + } + _, err = writer.Write(txt) + return err + } + + if str, ok := data.(error); ok { + _, err := writer.Write([]byte(str.Error())) + return err + } + + if str, ok := data.(fmt.Stringer); ok { + _, err := writer.Write([]byte(str.String())) + return err + } + + v := reflect.Indirect(reflect.ValueOf(data)) + if t := v.Type(); t.Kind() == reflect.Struct || t.Kind() == reflect.Slice { + b, err := swag.WriteJSON(data) + if err != nil { + return err + } + _, err = writer.Write(b) + return err + } + if v.Kind() != reflect.String { + return fmt.Errorf("%T is not a supported type by the TextProducer", data) } - _, err := buf.WriteTo(writer) + + _, err := writer.Write([]byte(v.String())) return err }) } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/text_test.go golang-github-go-openapi-runtime-0.15.0/text_test.go --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/text_test.go 2017-02-21 22:05:49.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/text_test.go 2018-06-28 22:01:56.000000000 +0000 @@ -16,6 +16,8 @@ import ( "bytes" + "errors" + "fmt" "net/http/httptest" "testing" @@ -26,10 +28,52 @@ func TestTextConsumer(t *testing.T) { cons := TextConsumer() - var data string - err := cons.Consume(bytes.NewBuffer([]byte(consProdText)), &data) - assert.NoError(t, err) - assert.Equal(t, consProdText, data) + + // can consume as a string + var str string + err1 := cons.Consume(bytes.NewBuffer([]byte(consProdText)), &str) + assert.NoError(t, err1) + assert.Equal(t, consProdText, str) + + var tu textUnmarshalDummy + + // can consume as a TextUnmarshaler + err3 := cons.Consume(bytes.NewBuffer([]byte(consProdText)), &tu) + assert.NoError(t, err3) + assert.Equal(t, consProdText, tu.str) + + // text unmarshal objects can return an error as well, this will be propagated + assert.Error(t, cons.Consume(bytes.NewBuffer(nil), &tu)) + + // when readers can't be read, those errors will be propogated as well + assert.Error(t, cons.Consume(new(nopReader), &tu)) + + // readers can also not be nil + assert.Error(t, cons.Consume(nil, &tu)) + + // can't consume nil ptr's or unsupported types + assert.Error(t, cons.Consume(bytes.NewBuffer([]byte(consProdText)), nil)) + assert.Error(t, cons.Consume(bytes.NewBuffer([]byte(consProdText)), 42)) + assert.Error(t, cons.Consume(bytes.NewBuffer([]byte(consProdText)), &struct{}{})) +} + +type textUnmarshalDummy struct { + str string +} + +func (t *textUnmarshalDummy) UnmarshalText(b []byte) error { + if len(b) == 0 { + return errors.New("no text given") + } + + t.str = string(b) + return nil +} + +type nopReader struct{} + +func (n *nopReader) Read(p []byte) (int, error) { + return 0, errors.New("nop") } func TestTextProducer(t *testing.T) { @@ -42,4 +86,91 @@ err2 := prod.Produce(rw2, &consProdText) assert.NoError(t, err2) assert.Equal(t, consProdText, rw2.Body.String()) + + // should always work with type aliases + // as an alias is sometimes given by generated go-swagger code + type alias string + aliasProdText := alias(consProdText) + rw3 := httptest.NewRecorder() + err3 := prod.Produce(rw3, aliasProdText) + assert.NoError(t, err3) + assert.Equal(t, consProdText, rw3.Body.String()) + rw4 := httptest.NewRecorder() + err4 := prod.Produce(rw4, &aliasProdText) + assert.NoError(t, err4) + assert.Equal(t, consProdText, rw4.Body.String()) + + const answer = "42" + + // Should always work with objects implementing Stringer interface + rw5 := httptest.NewRecorder() + err5 := prod.Produce(rw5, &stringerDummy{answer}) + assert.NoError(t, err5) + assert.Equal(t, answer, rw5.Body.String()) + + // Should always work with objects implementing TextMarshaler interface + rw6 := httptest.NewRecorder() + err6 := prod.Produce(rw6, &textMarshalDummy{answer}) + assert.NoError(t, err6) + assert.Equal(t, answer, rw6.Body.String()) + + rw10 := httptest.NewRecorder() + err10 := prod.Produce(rw10, errors.New(answer)) + assert.NoError(t, err10) + assert.Equal(t, answer, rw10.Body.String()) + + rw11 := httptest.NewRecorder() + err11 := prod.Produce(rw11, Error{Message: answer}) + assert.NoError(t, err11) + assert.Equal(t, fmt.Sprintf(`{"message":%q}`, answer), rw11.Body.String()) + + rw12 := httptest.NewRecorder() + err12 := prod.Produce(rw12, &Error{Message: answer}) + assert.NoError(t, err12) + assert.Equal(t, fmt.Sprintf(`{"message":%q}`, answer), rw12.Body.String()) + + rw13 := httptest.NewRecorder() + err13 := prod.Produce(rw13, []string{answer}) + assert.NoError(t, err13) + assert.Equal(t, fmt.Sprintf(`[%q]`, answer), rw13.Body.String()) + + // should not work with anything that's not (indirectly) a string + rw7 := httptest.NewRecorder() + err7 := prod.Produce(rw7, 42) + assert.Error(t, err7) + // nil values should also be safely caught with an error + rw8 := httptest.NewRecorder() + err8 := prod.Produce(rw8, nil) + assert.Error(t, err8) + + // writer can not be nil + assert.Error(t, prod.Produce(nil, &textMarshalDummy{answer})) + + // should not work for a textMarshaler that returns an error during marshalling + rw9 := httptest.NewRecorder() + err9 := prod.Produce(rw9, new(textMarshalDummy)) + assert.Error(t, err9) +} + +type Error struct { + Message string `json:"message"` +} + +type stringerDummy struct { + str string +} + +func (t *stringerDummy) String() string { + return t.str +} + +type textMarshalDummy struct { + str string +} + +func (t *textMarshalDummy) MarshalText() ([]byte, error) { + if t.str == "" { + return nil, errors.New("no text set") + } + return []byte(t.str), nil } diff -Nru golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.travis.yml golang-github-go-openapi-runtime-0.15.0/.travis.yml --- golang-github-go-openapi-runtime-0.0~git20160704.0.11e322e/.travis.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-go-openapi-runtime-0.15.0/.travis.yml 2018-06-28 22:01:56.000000000 +0000 @@ -0,0 +1,25 @@ +language: go +go: +- 1.8.x +- 1.9.x +- 1.10.x +install: +- go get -u github.com/axw/gocov/gocov +- go get -u gopkg.in/matm/v1/gocov-html +- go get -u github.com/cee-dub/go-junit-report +- go get -u github.com/stretchr/testify/assert +- go get -u golang.org/x/net/context +- go get -u gopkg.in/yaml.v2 +- go get -u github.com/go-openapi/analysis +- go get -u github.com/go-openapi/errors +- go get -u github.com/go-openapi/loads +- go get -u github.com/go-openapi/strfmt +- go get -u github.com/go-openapi/validate +- go get -u github.com/docker/go-units +script: +- ./hack/coverage +after_success: +- bash <(curl -s https://codecov.io/bash) +notifications: + slack: + secure: EmObnQuM9Mw8J9vpFaKKHqSMN4Wsr/A9+v7ewAD5cEhA0T1P4m7MbJMiJOhxUhj/X+BFh2DamW+P2lT8mybj5wg8wnkQ2BteKA8Tawi6f9PRw2NRheO8tAi8o/npLnlmet0kc93mn+oLuqHw36w4+j5mkOl2FghkfGiUVhwrhkCP7KXQN+3TU87e+/HzQumlJ3nsE+6terVxkH3PmaUTsS5ONaODZfuxFpfb7RsoEl3skHf6d+tr+1nViLxxly7558Nc33C+W1mr0qiEvMLZ+kJ/CpGWBJ6CUJM3jm6hNe2eMuIPwEK2hxZob8c7n22VPap4K6a0bBRoydoDXaba+2sD7Ym6ivDO/DVyL44VeBBLyIiIBylDGQdZH+6SoWm90Qe/i7tnY/T5Ao5igT8f3cfQY1c3EsTfqmlDfrhmACBmwSlgkdVBLTprHL63JMY24LWmh4jhxsmMRZhCL4dze8su1w6pLN/pD1pGHtKYCEVbdTmaM3PblNRFf12XB7qosmQsgUndH4Vq3bTbU0s1pKjeDhRyLvFzvR0TBbo0pDLEoF1A/i5GVFWa7yLZNUDudQERRh7qv/xBl2excIaQ1sV4DSVm7bAE9l6Kp+yeHQJW2uN6Y3X8wu9gB9nv9l5HBze7wh8KE6PyWAOLYYqZg9/sAtsv/2GcQqXcKFF1zcA=