...

Source file src/github.com/go-kivik/kivik/v4/couchdb/chttp/auth_test.go

Documentation: github.com/go-kivik/kivik/v4/couchdb/chttp

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  package chttp
    14  
    15  import (
    16  	"context"
    17  	"encoding/json"
    18  	"net/http"
    19  	"net/http/cookiejar"
    20  	"net/url"
    21  	"testing"
    22  
    23  	"gitlab.com/flimzy/testy"
    24  	"golang.org/x/net/publicsuffix"
    25  
    26  	kivik "github.com/go-kivik/kivik/v4"
    27  	internal "github.com/go-kivik/kivik/v4/int/errors"
    28  	"github.com/go-kivik/kivik/v4/int/mock"
    29  	"github.com/go-kivik/kivik/v4/internal/nettest"
    30  )
    31  
    32  type mockRT struct {
    33  	resp *http.Response
    34  	err  error
    35  }
    36  
    37  var _ http.RoundTripper = &mockRT{}
    38  
    39  func (rt *mockRT) RoundTrip(_ *http.Request) (*http.Response, error) {
    40  	return rt.resp, rt.err
    41  }
    42  
    43  func TestAuthenticate(t *testing.T) {
    44  	s := nettest.NewHTTPTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    45  		defer r.Body.Close() // nolint: errcheck
    46  		var authed bool
    47  		switch r.Header.Get("Authorization") {
    48  		case "Basic YWRtaW46YWJjMTIz", "Bearer tokennekot":
    49  			authed = true
    50  		}
    51  		if r.Method == http.MethodPost {
    52  			var result struct {
    53  				Name     string
    54  				Password string
    55  			}
    56  			_ = json.NewDecoder(r.Body).Decode(&result)
    57  			if result.Name == "admin" && result.Password == "abc123" {
    58  				authed = true
    59  				http.SetCookie(w, &http.Cookie{
    60  					Name:     kivik.SessionCookieName,
    61  					Value:    "auth-token",
    62  					Path:     "/",
    63  					HttpOnly: true,
    64  				})
    65  			}
    66  		}
    67  		if ses := r.Header.Get("Cookie"); ses == "AuthSession=auth-token" {
    68  			authed = true
    69  		}
    70  		if !authed {
    71  			w.WriteHeader(http.StatusUnauthorized)
    72  			return
    73  		}
    74  		w.WriteHeader(http.StatusOK)
    75  		if r.URL.Path == "/_session" { // nolint: goconst
    76  			_, _ = w.Write([]byte(`{"userCtx":{"name":"admin"}}`))
    77  			return
    78  		}
    79  		_, _ = w.Write([]byte(`{"foo":123}`))
    80  	}))
    81  
    82  	type authTest struct {
    83  		addr    string
    84  		jar     http.CookieJar
    85  		options kivik.Option
    86  		err     string
    87  		status  int
    88  	}
    89  	tests := testy.NewTable()
    90  	tests.Cleanup(s.Close)
    91  	tests.Add("unauthorized", authTest{
    92  		addr:   s.URL,
    93  		err:    "Unauthorized",
    94  		status: http.StatusUnauthorized,
    95  	})
    96  	tests.Add("basic auth", authTest{
    97  		addr:    s.URL,
    98  		options: BasicAuth("admin", "abc123"),
    99  	})
   100  	tests.Add("cookie auth success", func(t *testing.T) interface{} {
   101  		sv := nettest.NewHTTPTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   102  			h := w.Header()
   103  			h.Set("Content-Type", "application/json")
   104  			h.Set("Date", "Sat, 08 Sep 2018 15:49:29 GMT")
   105  			h.Set("Server", "CouchDB/2.2.0 (Erlang OTP/19)")
   106  			if r.URL.Path == "/_session" {
   107  				h.Set("Set-Cookie", "AuthSession=YWRtaW46NUI5M0VGODk6eLUGqXf0HRSEV9PPLaZX86sBYes; Version=1; Path=/; HttpOnly")
   108  				w.WriteHeader(200)
   109  				_, _ = w.Write([]byte(`{"ok":true,"name":"admin","roles":["_admin"]}`))
   110  			} else {
   111  				w.WriteHeader(200)
   112  				_, _ = w.Write([]byte(`{"ok":true}`))
   113  			}
   114  		}))
   115  		return authTest{
   116  			addr:    sv.URL,
   117  			options: CookieAuth("foo", "bar"),
   118  		}
   119  	})
   120  	tests.Add("failed basic auth", authTest{
   121  		addr:    s.URL,
   122  		options: BasicAuth("foo", ""),
   123  		err:     "Unauthorized",
   124  		status:  http.StatusUnauthorized,
   125  	})
   126  	tests.Add("failed cookie auth", authTest{
   127  		addr:    s.URL,
   128  		options: CookieAuth("foo", ""),
   129  		err:     `Get "?` + s.URL + `/foo"?: Unauthorized`,
   130  		status:  http.StatusUnauthorized,
   131  	})
   132  	tests.Add("already authenticated with cookie", func() interface{} {
   133  		jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
   134  		if err != nil {
   135  			t.Fatal(err)
   136  		}
   137  		u, _ := url.Parse(s.URL)
   138  		jar.SetCookies(u, []*http.Cookie{{
   139  			Name:     kivik.SessionCookieName,
   140  			Value:    "auth-token",
   141  			Path:     "/",
   142  			HttpOnly: true,
   143  		}})
   144  		return authTest{
   145  			addr: s.URL,
   146  			jar:  jar,
   147  		}
   148  	})
   149  	tests.Add("JWT auth", authTest{
   150  		addr:    s.URL,
   151  		options: JWTAuth("tokennekot"),
   152  	})
   153  	tests.Add("failed JWT auth", authTest{
   154  		addr:    s.URL,
   155  		options: JWTAuth("nekot"),
   156  		err:     "Unauthorized",
   157  		status:  http.StatusUnauthorized,
   158  	})
   159  
   160  	tests.Run(t, func(t *testing.T, test authTest) {
   161  		ctx := context.Background()
   162  		opts := test.options
   163  		if opts == nil {
   164  			opts = mock.NilOption
   165  		}
   166  		c, err := New(&http.Client{}, test.addr, opts)
   167  		if err != nil {
   168  			t.Fatal(err)
   169  		}
   170  		if test.jar != nil {
   171  			c.Client.Jar = test.jar
   172  		}
   173  		_, err = c.DoError(ctx, "GET", "/foo", nil)
   174  		if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
   175  			t.Error(d)
   176  		}
   177  	})
   178  }
   179  

View as plain text