diff --git a/Gopkg.lock b/Gopkg.lock index d77fac433ca..478b33adac0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -44,14 +44,6 @@ revision = "f7be6aa2bc7b2e38edf816b08b582782194a1c02" version = "v1.16.0" -[[projects]] - branch = "master" - digest = "1:df6450256d48d28ae22f47411292d2a68fed5b7dd9d09490a74b4b257170c975" - name = "github.com/Soontao/goHttpDigestClient" - packages = ["."] - pruneopts = "NUT" - revision = "6d28bb1415c5d06b76a4dcc4bb6e0dd39ce61af9" - [[projects]] digest = "1:fc86904a62ac4bfff8cfbe94f42231ce3d8cea8fe2506d5293eaef468f8eaecf" name = "github.com/andybalholm/cascadia" @@ -60,6 +52,14 @@ revision = "901648c87902174f774fac311d7f176f8647bdaa" version = "v1.0.0" +[[projects]] + branch = "master" + digest = "1:6a9ca0d9195f6b1e3b3e87036e6d1f1e34f62e836a53ca1bae10e14226330971" + name = "github.com/bobziuchkovski/digest" + packages = ["."] + pruneopts = "NUT" + revision = "26857231d60c9cfee5fbe14cd873bf7633ebbd80" + [[projects]] branch = "master" digest = "1:8a49786ec8f770dc1fdc251970f86e1f12b4938cf52b261ffa6f66158670c8c7" @@ -355,14 +355,6 @@ pruneopts = "NUT" revision = "00c29f56e2386353d58c599509e8dc3801b0d716" -[[projects]] - branch = "master" - digest = "1:16bbae9910129be5a18b951eabacd54414d3faea094dfab92bb3c84884d46b11" - name = "github.com/nu7hatch/gouuid" - packages = ["."] - pruneopts = "NUT" - revision = "179d4d0c4d8d407a32af483c2354df1d2c91e6c3" - [[projects]] branch = "master" digest = "1:8fe179a5468c41a57dd75dcb283c1a81c4ca38a934380ff996437033d7d9509a" @@ -657,7 +649,7 @@ "github.com/GeertJohan/go.rice/embedded", "github.com/PuerkitoBio/goquery", "github.com/Shopify/sarama", - "github.com/Soontao/goHttpDigestClient", + "github.com/bobziuchkovski/digest", "github.com/dop251/goja", "github.com/dop251/goja/parser", "github.com/dustin/go-humanize", diff --git a/Gopkg.toml b/Gopkg.toml index d00a71edf97..dd6a38a2e78 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -69,7 +69,7 @@ [[constraint]] branch = "master" - name = "github.com/Soontao/goHttpDigestClient" + name = "github.com/bobziuchkovski/digest" [[constraint]] branch = "master" diff --git a/lib/netext/httpext/request.go b/lib/netext/httpext/request.go index 253bf6fe7cd..8f360604943 100644 --- a/lib/netext/httpext/request.go +++ b/lib/netext/httpext/request.go @@ -38,7 +38,7 @@ import ( "time" ntlmssp "github.com/Azure/go-ntlmssp" - digest "github.com/Soontao/goHttpDigestClient" + "github.com/bobziuchkovski/digest" "github.com/loadimpact/k6/lib" "github.com/loadimpact/k6/stats" log "github.com/sirupsen/logrus" @@ -338,6 +338,18 @@ func MakeRequest(ctx context.Context, preq *ParsedHTTPRequest) (*Response, error } } + if preq.Auth == "digest" { + password, _ := preq.URL.u.User.Password() + transport = &digest.Transport{ + Username: preq.URL.u.User.Username(), + Password: password, + Transport: tracerTransport, + } + + // avoid sending the basic auth credentials + preq.Req.URL.User = nil + } + resp := &Response{ctx: ctx, URL: preq.URL.URL, Request: *respReq} client := http.Client{ Transport: transport, @@ -372,55 +384,6 @@ func MakeRequest(ctx context.Context, preq *ParsedHTTPRequest) (*Response, error }, } - // if digest authentication option is passed, make an initial request - // to get the authentication params to compute the authorization header - if preq.Auth == "digest" { - // TODO: fix - this is very broken! we're always making 2 HTTP requests - // when digest authentication is enabled... we should refactor it as a - // separate transport, like how NTLM auth works - // - // Github issue: https://github.com/loadimpact/k6/issues/800 - username := preq.URL.u.User.Username() - password, _ := preq.URL.u.User.Password() - - // removing user from URL to avoid sending the authorization header fo basic auth - preq.Req.URL.User = nil - - debugRequest(state, preq.Req, "DigestRequest") - res, err := client.Do(preq.Req.WithContext(ctx)) - debugResponse(state, res, "DigestResponse") - body, err := readResponseBody(state, ResponseTypeText, res, err) - finishedReq := tracerTransport.processLastSavedRequest() - if finishedReq != nil { - resp.ErrorCode = int(finishedReq.errorCode) - resp.Error = finishedReq.errorMsg - } - - if err != nil { - // Do *not* log errors about the contex being cancelled. - select { - case <-ctx.Done(): - default: - state.Logger.WithField("error", err).Warn("Digest request failed") - } - - // In case we have an error but resp.Error is not set it means the error is not from - // the transport. For all such errors currently we just return them as if throw is true - if preq.Throw || resp.Error == "" { - return nil, err - } - - return resp, nil - } - - if res.StatusCode == http.StatusUnauthorized { - challenge := digest.GetChallengeFromHeader(&res.Header) - challenge.ComputeResponse(preq.Req.Method, preq.Req.URL.RequestURI(), body.(string), username, password) - authorization := challenge.ToAuthorizationStr() - preq.Req.Header.Set(digest.KEY_AUTHORIZATION, authorization) - } - } - debugRequest(state, preq.Req, "Request") mreq := preq.Req.WithContext(ctx) res, resErr := client.Do(mreq) diff --git a/vendor/github.com/Soontao/goHttpDigestClient/LICENSE b/vendor/github.com/Soontao/goHttpDigestClient/LICENSE deleted file mode 100644 index 1493a1a4021..00000000000 --- a/vendor/github.com/Soontao/goHttpDigestClient/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Theo Sun - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/Soontao/goHttpDigestClient/challenge.go b/vendor/github.com/Soontao/goHttpDigestClient/challenge.go deleted file mode 100644 index f08fffb18e1..00000000000 --- a/vendor/github.com/Soontao/goHttpDigestClient/challenge.go +++ /dev/null @@ -1,96 +0,0 @@ -package goHttpDigestClient - -import ( - "fmt" - "strings" -) - -const ( - KEY_DIGEST = "Digest" - KEY_AUTH_SCHEMA = "auth_schema" - KEY_QOP = "qop" - KEY_NONCE = "nonce" - KEY_CNONCE = "cnonce" - KEY_USERNAME = "username" - KEY_NONCE_COUNT = "nc" - KEY_OPAQUE = "opaque" - KEY_RESPONSE = "response" - KEY_REALM = "realm" - KEY_AUTHORIZATION = "Authorization" - KEY_URI = "uri" - KEY_WWW_Authenticate = "WWW-Authenticate" -) - -//The 401 (Unauthorized) response message is used by an origin server -//to challenge the authorization of a user agent. -// -// And the CHALLENGE will include informations about auth -type Challenge map[string]string - -func NewChallenge(wwwAuthHeader string) Challenge { - r := Challenge{} - wwwAuthArr := strings.Split(strings.Replace(wwwAuthHeader, ",", "", -1), " ") - wwwAuthArrLen := len(wwwAuthArr) - if wwwAuthArrLen > 1 { - r[KEY_AUTH_SCHEMA] = wwwAuthArr[0] - for i := 1; i < wwwAuthArrLen; i++ { - itemArr := strings.Split(wwwAuthArr[i], "=") - r.SetChallengeItem(itemArr[0], itemArr[1]) - } - } - return r -} - -func (info Challenge) IsDigestAuth() bool { - return info[KEY_AUTH_SCHEMA] == KEY_DIGEST -} - -func (info Challenge) SetChallengeItem(itemKey string, itemValue string) { - info[itemKey] = itemValue -} - -func (info Challenge) GetChallengeItemPure(itemKey string) string { - return strings.Replace(info[itemKey], `"`, "", -1) -} - -// some specific key, will add qutation mark -func (info Challenge) GetChallengeItemFormat(itemKey string) string { - r := info.GetChallengeItemPure(itemKey) - switch itemKey { - case KEY_QOP, KEY_NONCE_COUNT: - return r - default: - return fmt.Sprintf(`"%s"`, r) - } -} - -// format challenge header to authorization header -// -// MAYBE you should computeResponseFirst() -func (info Challenge) ToAuthorizationStr() string { - auth_schema := KEY_DIGEST - authorization_content := "" - // how to specify the sequence - for k, _ := range info { - if k != KEY_AUTH_SCHEMA { - authorization_content += fmt.Sprintf(", %s=%s", k, info.GetChallengeItemFormat(k)) - } - } - return auth_schema + strings.Replace(authorization_content, ",", "", 1) -} - -// base challenge to compute the response, and the response will be checking by server -func (h Challenge) ComputeResponse(method, uri, entity, username, password string) Challenge { - qop := h.GetChallengeItemPure(KEY_QOP) - realm := h.GetChallengeItemPure(KEY_REALM) - nonce := h.GetChallengeItemPure(KEY_NONCE) - nonceCount := h.GetChallengeItemPure(KEY_NONCE_COUNT) - cNonce := h.GetChallengeItemPure(KEY_CNONCE) - response, cNonce, nonceCount := computeResponse(qop, realm, nonce, nonceCount, cNonce, method, uri, entity, username, password) - h.SetChallengeItem(KEY_USERNAME, username) - h.SetChallengeItem(KEY_URI, uri) - h.SetChallengeItem(KEY_CNONCE, cNonce) - h.SetChallengeItem(KEY_NONCE_COUNT, nonceCount) - h.SetChallengeItem(KEY_RESPONSE, response) - return h -} diff --git a/vendor/github.com/Soontao/goHttpDigestClient/client.go b/vendor/github.com/Soontao/goHttpDigestClient/client.go deleted file mode 100644 index 17ef351514b..00000000000 --- a/vendor/github.com/Soontao/goHttpDigestClient/client.go +++ /dev/null @@ -1,64 +0,0 @@ -package goHttpDigestClient - -import ( - "io" - "io/ioutil" - "net/http" -) - -// if option is set, get challenge at construct time -// if option not set, ever digest auth will send 2 request -type Client struct { - is_init bool - option ClientOption - http.Client -} - -type ClientOption struct { - username string - password string -} - -// create new Client instance -func NewClient(username, password string) *Client { - opt := &ClientOption{username: username, password: password} - // here need more attention - return &Client{option: *opt, is_init: false} -} - -func GetChallengeFromHeader(h *http.Header) Challenge { - return NewChallenge(h.Get(KEY_WWW_Authenticate)) -} - -func (c *Client) Do(req *http.Request, opt *ClientOption) (*http.Response, error) { - res, err := c.Client.Do(req) - if res.StatusCode == http.StatusUnauthorized { - challenge := GetChallengeFromHeader(&res.Header) - challenge.ComputeResponse(req.Method, req.URL.RequestURI(), getStrFromIO(req.Body), opt.username, opt.password) - authorization := challenge.ToAuthorizationStr() - req.Header.Set(KEY_AUTHORIZATION, authorization) - return c.Client.Do(req) - } else { - return res, err - } -} - -// From ReadCloser to string -func getStrFromIO(r io.ReadCloser) string { - if r == nil { - return "" - } - if b, err := ioutil.ReadAll(r); err == nil { - return string(b) - } else { - return "" - } -} - -// static Defualt Client -var DefaultClient = &Client{is_init: true} - -// Default Client Do Request -func Do(req *http.Request, opt *ClientOption) (*http.Response, error) { - return DefaultClient.Do(req, opt) -} diff --git a/vendor/github.com/Soontao/goHttpDigestClient/rfc2617.go b/vendor/github.com/Soontao/goHttpDigestClient/rfc2617.go deleted file mode 100644 index bdc1abfa005..00000000000 --- a/vendor/github.com/Soontao/goHttpDigestClient/rfc2617.go +++ /dev/null @@ -1,48 +0,0 @@ -package goHttpDigestClient - -import ( - "crypto/md5" - "fmt" - "github.com/nu7hatch/gouuid" - "strings" -) - -// hash any string to md5 hex string -func toMd5(s string) string { - sByte := []byte(s) - return fmt.Sprintf("%x", md5.Sum(sByte)) -} - -//In RFC 2617 -// -//HA1 is equal to MD5("username:realm:password") -func computeHa1(username, realm, password string) string { - return toMd5(fmt.Sprintf("%s:%s:%s", username, realm, password)) -} - -func computeHa2(qop, method, digestUri, entity string) string { - switch qop { - case "auth-int": - return toMd5(fmt.Sprintf("%s:%s:%s", method, digestUri, toMd5(entity))) - default: - return toMd5(fmt.Sprintf("%s:%s", method, digestUri)) - } -} - -func computeResponse(qop, realm, nonce, nonceCount, clientNonce, method, uri, entity, username, password string) (response, cNonce, nc string) { - if clientNonce == "" { - newUUID, _ := uuid.NewV4() - clientNonce = strings.Replace(newUUID.String(), "-", "", -1) - } - if nonceCount == "" { - nonceCount = "00000001" - } - ha1 := computeHa1(username, realm, password) - ha2 := computeHa2(qop, method, uri, entity) - switch qop { - case "auth", "auth-int": - return toMd5(fmt.Sprintf("%s:%s:%s:%s:%s:%s", ha1, nonce, nonceCount, clientNonce, qop, ha2)), clientNonce, nonceCount - default: - return toMd5(fmt.Sprintf("%s:%s:%s", ha1, nonce, ha2)), clientNonce, nonceCount - } -} diff --git a/vendor/github.com/bobziuchkovski/digest/CONTRIBUTORS b/vendor/github.com/bobziuchkovski/digest/CONTRIBUTORS new file mode 100644 index 00000000000..2db3b3e0494 --- /dev/null +++ b/vendor/github.com/bobziuchkovski/digest/CONTRIBUTORS @@ -0,0 +1,17 @@ +# This is the official list of people who can contribute +# (and typically have contributed) code to the mlab-ns2 +# repository. +# +# Names should be added to this file like so: +# Name +# +# An entry with two email addresses specifies that the +# first address should be used in the submit logs and +# that the second address should be recognized as the +# same person when interacting with Rietveld. + +# Please keep the list sorted. + +Bipasa Chattopadhyay +Eric Gavaletz +Seon-Wook Park diff --git a/vendor/github.com/bobziuchkovski/digest/COPYING b/vendor/github.com/bobziuchkovski/digest/COPYING new file mode 100644 index 00000000000..f433b1a53f5 --- /dev/null +++ b/vendor/github.com/bobziuchkovski/digest/COPYING @@ -0,0 +1,177 @@ + + 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 diff --git a/vendor/github.com/bobziuchkovski/digest/digest.go b/vendor/github.com/bobziuchkovski/digest/digest.go new file mode 100644 index 00000000000..89caa31ae05 --- /dev/null +++ b/vendor/github.com/bobziuchkovski/digest/digest.go @@ -0,0 +1,275 @@ +// Copyright 2013 M-Lab +// +// 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. + +// The digest package provides an implementation of http.RoundTripper that takes +// care of HTTP Digest Authentication (http://www.ietf.org/rfc/rfc2617.txt). +// This only implements the MD5 and "auth" portions of the RFC, but that covers +// the majority of avalible server side implementations including apache web +// server. +// +// Example usage: +// +// t := NewTransport("myUserName", "myP@55w0rd") +// req, err := http.NewRequest("GET", "http://notreal.com/path?arg=1", nil) +// if err != nil { +// return err +// } +// resp, err := t.RoundTrip(req) +// if err != nil { +// return err +// } +// +// OR it can be used as a client: +// +// c, err := t.Client() +// if err != nil { +// return err +// } +// resp, err := c.Get("http://notreal.com/path?arg=1") +// if err != nil { +// return err +// } +// +package digest + +import ( + "crypto/md5" + "crypto/rand" + "errors" + "fmt" + "io" + "net/http" + "strings" +) + +var ( + ErrNilTransport = errors.New("Transport is nil") + ErrBadChallenge = errors.New("Challenge is bad") + ErrAlgNotImplemented = errors.New("Alg not implemented") +) + +// Transport is an implementation of http.RoundTripper that takes care of http +// digest authentication. +type Transport struct { + Username string + Password string + Transport http.RoundTripper +} + +// NewTransport creates a new digest transport using the http.DefaultTransport. +func NewTransport(username, password string) *Transport { + t := &Transport{ + Username: username, + Password: password, + } + t.Transport = http.DefaultTransport + return t +} + +type challenge struct { + Realm string + Domain string + Nonce string + Opaque string + Stale string + Algorithm string + Qop string +} + +func parseChallenge(input string) (*challenge, error) { + const ws = " \n\r\t" + const qs = `"` + s := strings.Trim(input, ws) + if !strings.HasPrefix(s, "Digest ") { + return nil, ErrBadChallenge + } + s = strings.Trim(s[7:], ws) + sl := strings.Split(s, ", ") + c := &challenge{ + Algorithm: "MD5", + } + var r []string + for i := range sl { + r = strings.SplitN(sl[i], "=", 2) + switch r[0] { + case "realm": + c.Realm = strings.Trim(r[1], qs) + case "domain": + c.Domain = strings.Trim(r[1], qs) + case "nonce": + c.Nonce = strings.Trim(r[1], qs) + case "opaque": + c.Opaque = strings.Trim(r[1], qs) + case "stale": + c.Stale = strings.Trim(r[1], qs) + case "algorithm": + c.Algorithm = strings.Trim(r[1], qs) + case "qop": + //TODO(gavaletz) should be an array of strings? + c.Qop = strings.Trim(r[1], qs) + default: + return nil, ErrBadChallenge + } + } + return c, nil +} + +type credentials struct { + Username string + Realm string + Nonce string + DigestURI string + Algorithm string + Cnonce string + Opaque string + MessageQop string + NonceCount int + method string + password string +} + +func h(data string) string { + hf := md5.New() + io.WriteString(hf, data) + return fmt.Sprintf("%x", hf.Sum(nil)) +} + +func kd(secret, data string) string { + return h(fmt.Sprintf("%s:%s", secret, data)) +} + +func (c *credentials) ha1() string { + return h(fmt.Sprintf("%s:%s:%s", c.Username, c.Realm, c.password)) +} + +func (c *credentials) ha2() string { + return h(fmt.Sprintf("%s:%s", c.method, c.DigestURI)) +} + +func (c *credentials) resp(cnonce string) (string, error) { + c.NonceCount++ + if c.MessageQop == "auth" { + if cnonce != "" { + c.Cnonce = cnonce + } else { + b := make([]byte, 8) + io.ReadFull(rand.Reader, b) + c.Cnonce = fmt.Sprintf("%x", b)[:16] + } + return kd(c.ha1(), fmt.Sprintf("%s:%08x:%s:%s:%s", + c.Nonce, c.NonceCount, c.Cnonce, c.MessageQop, c.ha2())), nil + } else if c.MessageQop == "" { + return kd(c.ha1(), fmt.Sprintf("%s:%s", c.Nonce, c.ha2())), nil + } + return "", ErrAlgNotImplemented +} + +func (c *credentials) authorize() (string, error) { + // Note that this is only implemented for MD5 and NOT MD5-sess. + // MD5-sess is rarely supported and those that do are a big mess. + if c.Algorithm != "MD5" { + return "", ErrAlgNotImplemented + } + // Note that this is NOT implemented for "qop=auth-int". Similarly the + // auth-int server side implementations that do exist are a mess. + if c.MessageQop != "auth" && c.MessageQop != "" { + return "", ErrAlgNotImplemented + } + resp, err := c.resp("") + if err != nil { + return "", ErrAlgNotImplemented + } + sl := []string{fmt.Sprintf(`username="%s"`, c.Username)} + sl = append(sl, fmt.Sprintf(`realm="%s"`, c.Realm)) + sl = append(sl, fmt.Sprintf(`nonce="%s"`, c.Nonce)) + sl = append(sl, fmt.Sprintf(`uri="%s"`, c.DigestURI)) + sl = append(sl, fmt.Sprintf(`response="%s"`, resp)) + if c.Algorithm != "" { + sl = append(sl, fmt.Sprintf(`algorithm="%s"`, c.Algorithm)) + } + if c.Opaque != "" { + sl = append(sl, fmt.Sprintf(`opaque="%s"`, c.Opaque)) + } + if c.MessageQop != "" { + sl = append(sl, fmt.Sprintf("qop=%s", c.MessageQop)) + sl = append(sl, fmt.Sprintf("nc=%08x", c.NonceCount)) + sl = append(sl, fmt.Sprintf(`cnonce="%s"`, c.Cnonce)) + } + return fmt.Sprintf("Digest %s", strings.Join(sl, ", ")), nil +} + +func (t *Transport) newCredentials(req *http.Request, c *challenge) *credentials { + return &credentials{ + Username: t.Username, + Realm: c.Realm, + Nonce: c.Nonce, + DigestURI: req.URL.RequestURI(), + Algorithm: c.Algorithm, + Opaque: c.Opaque, + MessageQop: c.Qop, // "auth" must be a single value + NonceCount: 0, + method: req.Method, + password: t.Password, + } +} + +// RoundTrip makes a request expecting a 401 response that will require digest +// authentication. It creates the credentials it needs and makes a follow-up +// request. +func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + if t.Transport == nil { + return nil, ErrNilTransport + } + + // Copy the request so we don't modify the input. + req2 := new(http.Request) + *req2 = *req + req2.Header = make(http.Header) + for k, s := range req.Header { + req2.Header[k] = s + } + + // Make a request to get the 401 that contains the challenge. + resp, err := t.Transport.RoundTrip(req) + if err != nil || resp.StatusCode != 401 { + return resp, err + } + chal := resp.Header.Get("WWW-Authenticate") + c, err := parseChallenge(chal) + if err != nil { + return resp, err + } + + // Form credentials based on the challenge. + cr := t.newCredentials(req2, c) + auth, err := cr.authorize() + if err != nil { + return resp, err + } + + // We'll no longer use the initial response, so close it + resp.Body.Close() + + // Make authenticated request. + req2.Header.Set("Authorization", auth) + return t.Transport.RoundTrip(req2) +} + +// Client returns an HTTP client that uses the digest transport. +func (t *Transport) Client() (*http.Client, error) { + if t.Transport == nil { + return nil, ErrNilTransport + } + return &http.Client{Transport: t}, nil +} diff --git a/vendor/github.com/nu7hatch/gouuid/COPYING b/vendor/github.com/nu7hatch/gouuid/COPYING deleted file mode 100644 index d7849fd8fb8..00000000000 --- a/vendor/github.com/nu7hatch/gouuid/COPYING +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2011 by Krzysztof Kowalik - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/nu7hatch/gouuid/uuid.go b/vendor/github.com/nu7hatch/gouuid/uuid.go deleted file mode 100644 index ac9623b729f..00000000000 --- a/vendor/github.com/nu7hatch/gouuid/uuid.go +++ /dev/null @@ -1,173 +0,0 @@ -// This package provides immutable UUID structs and the functions -// NewV3, NewV4, NewV5 and Parse() for generating versions 3, 4 -// and 5 UUIDs as specified in RFC 4122. -// -// Copyright (C) 2011 by Krzysztof Kowalik -package uuid - -import ( - "crypto/md5" - "crypto/rand" - "crypto/sha1" - "encoding/hex" - "errors" - "fmt" - "hash" - "regexp" -) - -// The UUID reserved variants. -const ( - ReservedNCS byte = 0x80 - ReservedRFC4122 byte = 0x40 - ReservedMicrosoft byte = 0x20 - ReservedFuture byte = 0x00 -) - -// The following standard UUIDs are for use with NewV3() or NewV5(). -var ( - NamespaceDNS, _ = ParseHex("6ba7b810-9dad-11d1-80b4-00c04fd430c8") - NamespaceURL, _ = ParseHex("6ba7b811-9dad-11d1-80b4-00c04fd430c8") - NamespaceOID, _ = ParseHex("6ba7b812-9dad-11d1-80b4-00c04fd430c8") - NamespaceX500, _ = ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8") -) - -// Pattern used to parse hex string representation of the UUID. -// FIXME: do something to consider both brackets at one time, -// current one allows to parse string with only one opening -// or closing bracket. -const hexPattern = "^(urn\\:uuid\\:)?\\{?([a-z0-9]{8})-([a-z0-9]{4})-" + - "([1-5][a-z0-9]{3})-([a-z0-9]{4})-([a-z0-9]{12})\\}?$" - -var re = regexp.MustCompile(hexPattern) - -// A UUID representation compliant with specification in -// RFC 4122 document. -type UUID [16]byte - -// ParseHex creates a UUID object from given hex string -// representation. Function accepts UUID string in following -// formats: -// -// uuid.ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8") -// uuid.ParseHex("{6ba7b814-9dad-11d1-80b4-00c04fd430c8}") -// uuid.ParseHex("urn:uuid:6ba7b814-9dad-11d1-80b4-00c04fd430c8") -// -func ParseHex(s string) (u *UUID, err error) { - md := re.FindStringSubmatch(s) - if md == nil { - err = errors.New("Invalid UUID string") - return - } - hash := md[2] + md[3] + md[4] + md[5] + md[6] - b, err := hex.DecodeString(hash) - if err != nil { - return - } - u = new(UUID) - copy(u[:], b) - return -} - -// Parse creates a UUID object from given bytes slice. -func Parse(b []byte) (u *UUID, err error) { - if len(b) != 16 { - err = errors.New("Given slice is not valid UUID sequence") - return - } - u = new(UUID) - copy(u[:], b) - return -} - -// Generate a UUID based on the MD5 hash of a namespace identifier -// and a name. -func NewV3(ns *UUID, name []byte) (u *UUID, err error) { - if ns == nil { - err = errors.New("Invalid namespace UUID") - return - } - u = new(UUID) - // Set all bits to MD5 hash generated from namespace and name. - u.setBytesFromHash(md5.New(), ns[:], name) - u.setVariant(ReservedRFC4122) - u.setVersion(3) - return -} - -// Generate a random UUID. -func NewV4() (u *UUID, err error) { - u = new(UUID) - // Set all bits to randomly (or pseudo-randomly) chosen values. - _, err = rand.Read(u[:]) - if err != nil { - return - } - u.setVariant(ReservedRFC4122) - u.setVersion(4) - return -} - -// Generate a UUID based on the SHA-1 hash of a namespace identifier -// and a name. -func NewV5(ns *UUID, name []byte) (u *UUID, err error) { - u = new(UUID) - // Set all bits to truncated SHA1 hash generated from namespace - // and name. - u.setBytesFromHash(sha1.New(), ns[:], name) - u.setVariant(ReservedRFC4122) - u.setVersion(5) - return -} - -// Generate a MD5 hash of a namespace and a name, and copy it to the -// UUID slice. -func (u *UUID) setBytesFromHash(hash hash.Hash, ns, name []byte) { - hash.Write(ns[:]) - hash.Write(name) - copy(u[:], hash.Sum([]byte{})[:16]) -} - -// Set the two most significant bits (bits 6 and 7) of the -// clock_seq_hi_and_reserved to zero and one, respectively. -func (u *UUID) setVariant(v byte) { - switch v { - case ReservedNCS: - u[8] = (u[8] | ReservedNCS) & 0xBF - case ReservedRFC4122: - u[8] = (u[8] | ReservedRFC4122) & 0x7F - case ReservedMicrosoft: - u[8] = (u[8] | ReservedMicrosoft) & 0x3F - } -} - -// Variant returns the UUID Variant, which determines the internal -// layout of the UUID. This will be one of the constants: RESERVED_NCS, -// RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE. -func (u *UUID) Variant() byte { - if u[8]&ReservedNCS == ReservedNCS { - return ReservedNCS - } else if u[8]&ReservedRFC4122 == ReservedRFC4122 { - return ReservedRFC4122 - } else if u[8]&ReservedMicrosoft == ReservedMicrosoft { - return ReservedMicrosoft - } - return ReservedFuture -} - -// Set the four most significant bits (bits 12 through 15) of the -// time_hi_and_version field to the 4-bit version number. -func (u *UUID) setVersion(v byte) { - u[6] = (u[6] & 0xF) | (v << 4) -} - -// Version returns a version number of the algorithm used to -// generate the UUID sequence. -func (u *UUID) Version() uint { - return uint(u[6] >> 4) -} - -// Returns unparsed version of the generated UUID sequence. -func (u *UUID) String() string { - return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) -}