github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/api_definition_test.go (about)

     1  package gateway
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"net"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/TykTechnologies/tyk/apidef"
    14  	"github.com/TykTechnologies/tyk/config"
    15  	"github.com/TykTechnologies/tyk/test"
    16  	"github.com/TykTechnologies/tyk/user"
    17  	"github.com/go-redis/redis"
    18  )
    19  
    20  func TestURLRewrites(t *testing.T) {
    21  	ts := StartTest()
    22  	defer ts.Close()
    23  
    24  	t.Run("Extended Paths with url_rewrites", func(t *testing.T) {
    25  		BuildAndLoadAPI(func(spec *APISpec) {
    26  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
    27  				json.Unmarshal([]byte(`[
    28  						{
    29                              "path": "/rewrite1",
    30                              "method": "GET",
    31                              "match_pattern": "/rewrite1",
    32                              "rewrite_to": "",
    33                              "triggers": [
    34                                  {
    35                                      "on": "all",
    36                                      "options": {
    37                                          "header_matches": {},
    38                                          "query_val_matches": {
    39                                              "show_env": {
    40                                                  "match_rx": "1"
    41                                              }
    42                                          },
    43                                          "path_part_matches": {},
    44                                          "session_meta_matches": {},
    45                                          "payload_matches": {
    46                                              "match_rx": ""
    47                                          }
    48                                      },
    49                                      "rewrite_to": "/get?show_env=2"
    50                                  }
    51                              ],
    52                              "MatchRegexp": null
    53                          },
    54                          {
    55                              "path": "/rewrite",
    56                              "method": "GET",
    57                              "match_pattern": "/rewrite",
    58                              "rewrite_to": "/get?just_rewrite",
    59                              "triggers": [],
    60                              "MatchRegexp": null
    61  						}
    62  				]`), &v.ExtendedPaths.URLRewrite)
    63  			})
    64  			spec.Proxy.ListenPath = "/"
    65  		})
    66  
    67  		ts.Run(t, []test.TestCase{
    68  			{Path: "/rewrite1?show_env=1", Code: http.StatusOK, BodyMatch: `"URI":"/get\?show_env=2"`},
    69  			{Path: "/rewrite", Code: http.StatusOK, BodyMatch: `"URI":"/get\?just_rewrite"`},
    70  		}...)
    71  	})
    72  }
    73  
    74  func TestWhitelist(t *testing.T) {
    75  	ts := StartTest()
    76  	defer ts.Close()
    77  
    78  	t.Run("Extended Paths", func(t *testing.T) {
    79  		BuildAndLoadAPI(func(spec *APISpec) {
    80  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
    81  				json.Unmarshal([]byte(`[
    82  					{
    83  						"path": "/reply/{id}",
    84  						"method_actions": {
    85  							"GET": {"action": "reply", "code": 200, "data": "flump"}
    86  						}
    87  					},
    88  					{
    89  						"path": "/get",
    90  						"method_actions": {"GET": {"action": "no_action"}}
    91  					}
    92  				]`), &v.ExtendedPaths.WhiteList)
    93  			})
    94  
    95  			spec.Proxy.ListenPath = "/"
    96  		})
    97  
    98  		ts.Run(t, []test.TestCase{
    99  			// Should mock path
   100  			{Path: "/reply/", Code: http.StatusOK, BodyMatch: "flump"},
   101  			{Path: "/reply/123", Code: http.StatusOK, BodyMatch: "flump"},
   102  			// Should get original upstream response
   103  			{Path: "/get", Code: http.StatusOK, BodyMatch: `"Url":"/get"`},
   104  			// Reject not whitelisted (but know by upstream) path
   105  			{Method: "POST", Path: "/post", Code: http.StatusForbidden},
   106  		}...)
   107  	})
   108  
   109  	t.Run("Simple Paths", func(t *testing.T) {
   110  		BuildAndLoadAPI(func(spec *APISpec) {
   111  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   112  				v.Paths.WhiteList = []string{"/simple", "/regex/{id}/test"}
   113  				v.UseExtendedPaths = false
   114  			})
   115  
   116  			spec.Proxy.ListenPath = "/"
   117  		})
   118  
   119  		ts.Run(t, []test.TestCase{
   120  			// Should mock path
   121  			{Path: "/simple", Code: http.StatusOK},
   122  			{Path: "/regex/123/test", Code: http.StatusOK},
   123  			{Path: "/regex/123/differ", Code: http.StatusForbidden},
   124  			{Path: "/", Code: http.StatusForbidden},
   125  		}...)
   126  	})
   127  
   128  	t.Run("Test #1944", func(t *testing.T) {
   129  		BuildAndLoadAPI(func(spec *APISpec) {
   130  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   131  				v.Paths.WhiteList = []string{"/foo/{fooId}$", "/foo/{fooId}/bar/{barId}$", "/baz/{bazId}"}
   132  				v.UseExtendedPaths = false
   133  			})
   134  
   135  			spec.Proxy.ListenPath = "/"
   136  		})
   137  
   138  		ts.Run(t, []test.TestCase{
   139  			{Path: "/foo", Code: http.StatusForbidden},
   140  			{Path: "/foo/", Code: http.StatusOK},
   141  			{Path: "/foo/1", Code: http.StatusOK},
   142  			{Path: "/foo/1/bar", Code: http.StatusForbidden},
   143  			{Path: "/foo/1/bar/", Code: http.StatusOK},
   144  			{Path: "/foo/1/bar/1", Code: http.StatusOK},
   145  			{Path: "/", Code: http.StatusForbidden},
   146  			{Path: "/baz", Code: http.StatusForbidden},
   147  			{Path: "/baz/", Code: http.StatusOK},
   148  			{Path: "/baz/1", Code: http.StatusOK},
   149  			{Path: "/baz/1/", Code: http.StatusOK},
   150  			{Path: "/baz/1/bazz", Code: http.StatusOK},
   151  		}...)
   152  	})
   153  
   154  	t.Run("Case Sensitivity", func(t *testing.T) {
   155  		BuildAndLoadAPI(func(spec *APISpec) {
   156  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   157  				v.Paths.WhiteList = []string{"/Foo", "/bar"}
   158  				v.UseExtendedPaths = false
   159  			})
   160  
   161  			spec.Proxy.ListenPath = "/"
   162  		})
   163  
   164  		ts.Run(t, []test.TestCase{
   165  			{Path: "/foo", Code: http.StatusForbidden},
   166  			{Path: "/Foo", Code: http.StatusOK},
   167  			{Path: "/bar", Code: http.StatusOK},
   168  			{Path: "/Bar", Code: http.StatusForbidden},
   169  		}...)
   170  	})
   171  }
   172  
   173  func TestBlacklist(t *testing.T) {
   174  	ts := StartTest()
   175  	defer ts.Close()
   176  
   177  	t.Run("Extended Paths", func(t *testing.T) {
   178  		BuildAndLoadAPI(func(spec *APISpec) {
   179  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   180  				json.Unmarshal([]byte(`[
   181  					{
   182  						"path": "/blacklist/literal",
   183  						"method_actions": {"GET": {"action": "no_action"}}
   184  					},
   185  					{
   186  						"path": "/blacklist/{id}/test",
   187  						"method_actions": {"GET": {"action": "no_action"}}
   188  					}
   189  				]`), &v.ExtendedPaths.BlackList)
   190  			})
   191  
   192  			spec.Proxy.ListenPath = "/"
   193  		})
   194  
   195  		ts.Run(t, []test.TestCase{
   196  			{Path: "/blacklist/literal", Code: http.StatusForbidden},
   197  			{Path: "/blacklist/123/test", Code: http.StatusForbidden},
   198  
   199  			{Path: "/blacklist/123/different", Code: http.StatusOK},
   200  			// POST method not blacklisted
   201  			{Method: "POST", Path: "/blacklist/literal", Code: http.StatusOK},
   202  		}...)
   203  	})
   204  
   205  	t.Run("Simple Paths", func(t *testing.T) {
   206  		BuildAndLoadAPI(func(spec *APISpec) {
   207  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   208  				v.Paths.BlackList = []string{"/blacklist/literal", "/blacklist/{id}/test"}
   209  				v.UseExtendedPaths = false
   210  			})
   211  
   212  			spec.Proxy.ListenPath = "/"
   213  		})
   214  
   215  		ts.Run(t, []test.TestCase{
   216  			{Path: "/blacklist/literal", Code: http.StatusForbidden},
   217  			{Path: "/blacklist/123/test", Code: http.StatusForbidden},
   218  
   219  			{Path: "/blacklist/123/different", Code: http.StatusOK},
   220  			// POST method also blacklisted
   221  			{Method: "POST", Path: "/blacklist/literal", Code: http.StatusForbidden},
   222  		}...)
   223  	})
   224  
   225  	t.Run("Case Sensitivity", func(t *testing.T) {
   226  		BuildAndLoadAPI(func(spec *APISpec) {
   227  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   228  				v.Paths.BlackList = []string{"/Foo", "/bar"}
   229  				v.UseExtendedPaths = false
   230  			})
   231  
   232  			spec.Proxy.ListenPath = "/"
   233  		})
   234  
   235  		ts.Run(t, []test.TestCase{
   236  			{Path: "/foo", Code: http.StatusOK},
   237  			{Path: "/Foo", Code: http.StatusForbidden},
   238  			{Path: "/bar", Code: http.StatusForbidden},
   239  			{Path: "/Bar", Code: http.StatusOK},
   240  		}...)
   241  	})
   242  }
   243  
   244  func TestConflictingPaths(t *testing.T) {
   245  	ts := StartTest()
   246  	defer ts.Close()
   247  
   248  	BuildAndLoadAPI(func(spec *APISpec) {
   249  		UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   250  			json.Unmarshal([]byte(`[
   251  				{
   252  					"path": "/metadata/{id}",
   253  					"method_actions": {"GET": {"action": "no_action"}}
   254  				},
   255  				{
   256  					"path": "/metadata/purge",
   257  					"method_actions": {"POST": {"action": "no_action"}}
   258  				}
   259  			]`), &v.ExtendedPaths.WhiteList)
   260  		})
   261  
   262  		spec.Proxy.ListenPath = "/"
   263  	})
   264  
   265  	ts.Run(t, []test.TestCase{
   266  		// Should ignore auth check
   267  		{Method: "POST", Path: "/customer-servicing/documents/metadata/purge", Code: http.StatusOK},
   268  		{Method: "GET", Path: "/customer-servicing/documents/metadata/{id}", Code: http.StatusOK},
   269  	}...)
   270  }
   271  
   272  func TestIgnored(t *testing.T) {
   273  	ts := StartTest()
   274  	defer ts.Close()
   275  
   276  	t.Run("Extended Paths", func(t *testing.T) {
   277  		BuildAndLoadAPI(func(spec *APISpec) {
   278  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   279  				json.Unmarshal([]byte(`[
   280  					{
   281  						"path": "/ignored/literal",
   282  						"method_actions": {"GET": {"action": "no_action"}}
   283  					},
   284  					{
   285  						"path": "/ignored/{id}/test",
   286  						"method_actions": {"GET": {"action": "no_action"}}
   287  					}
   288  				]`), &v.ExtendedPaths.Ignored)
   289  			})
   290  
   291  			spec.UseKeylessAccess = false
   292  			spec.Proxy.ListenPath = "/"
   293  		})
   294  
   295  		ts.Run(t, []test.TestCase{
   296  			// Should ignore auth check
   297  			{Path: "/ignored/literal", Code: http.StatusOK},
   298  			{Path: "/ignored/123/test", Code: http.StatusOK},
   299  			// Only GET is ignored
   300  			{Method: "POST", Path: "/ext/ignored/literal", Code: 401},
   301  
   302  			{Path: "/", Code: 401},
   303  		}...)
   304  	})
   305  
   306  	t.Run("Simple Paths", func(t *testing.T) {
   307  		BuildAndLoadAPI(func(spec *APISpec) {
   308  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   309  				v.Paths.Ignored = []string{"/ignored/literal", "/ignored/{id}/test"}
   310  				v.UseExtendedPaths = false
   311  			})
   312  
   313  			spec.UseKeylessAccess = false
   314  			spec.Proxy.ListenPath = "/"
   315  		})
   316  
   317  		ts.Run(t, []test.TestCase{
   318  			// Should ignore auth check
   319  			{Path: "/ignored/literal", Code: http.StatusOK},
   320  			{Path: "/ignored/123/test", Code: http.StatusOK},
   321  			// All methods ignored
   322  			{Method: "POST", Path: "/ext/ignored/literal", Code: http.StatusOK},
   323  
   324  			{Path: "/", Code: 401},
   325  		}...)
   326  	})
   327  
   328  	t.Run("With URL rewrite", func(t *testing.T) {
   329  		BuildAndLoadAPI(func(spec *APISpec) {
   330  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   331  				v.ExtendedPaths.URLRewrite = []apidef.URLRewriteMeta{{
   332  					Path:         "/ignored",
   333  					Method:       "GET",
   334  					MatchPattern: "/ignored",
   335  					RewriteTo:    "/get",
   336  				}}
   337  				v.ExtendedPaths.Ignored = []apidef.EndPointMeta{
   338  					{
   339  						Path: "ignored",
   340  						MethodActions: map[string]apidef.EndpointMethodMeta{
   341  							http.MethodGet: {
   342  								Action: apidef.NoAction,
   343  								Code:   http.StatusOK,
   344  							},
   345  						},
   346  					},
   347  				}
   348  				v.UseExtendedPaths = true
   349  			})
   350  
   351  			spec.UseKeylessAccess = false
   352  			spec.Proxy.ListenPath = "/"
   353  		})
   354  
   355  		_, _ = ts.Run(t, []test.TestCase{
   356  			// URL rewrite should work with ignore
   357  			{Path: "/ignored", BodyMatch: `"URI":"/get"`, Code: http.StatusOK},
   358  			{Path: "/", Code: http.StatusUnauthorized},
   359  		}...)
   360  	})
   361  
   362  	t.Run("Case Sensitivity", func(t *testing.T) {
   363  		spec := BuildAPI(func(spec *APISpec) {
   364  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   365  				v.ExtendedPaths.Ignored = []apidef.EndPointMeta{{Path: "/Foo", IgnoreCase: false}, {Path: "/bar", IgnoreCase: true}}
   366  				v.UseExtendedPaths = true
   367  			})
   368  
   369  			spec.UseKeylessAccess = false
   370  			spec.Proxy.ListenPath = "/"
   371  		})[0]
   372  
   373  		LoadAPI(spec)
   374  
   375  		_, _ = ts.Run(t, []test.TestCase{
   376  			{Path: "/foo", Code: http.StatusUnauthorized},
   377  			{Path: "/Foo", Code: http.StatusOK},
   378  			{Path: "/bar", Code: http.StatusOK},
   379  			{Path: "/Bar", Code: http.StatusOK},
   380  		}...)
   381  
   382  		t.Run("ignore-case globally", func(t *testing.T) {
   383  			globalConf := config.Global()
   384  			globalConf.IgnoreEndpointCase = true
   385  			config.SetGlobal(globalConf)
   386  
   387  			LoadAPI(spec)
   388  
   389  			_, _ = ts.Run(t, []test.TestCase{
   390  				{Path: "/foo", Code: http.StatusOK},
   391  				{Path: "/Foo", Code: http.StatusOK},
   392  				{Path: "/bar", Code: http.StatusOK},
   393  				{Path: "/Bar", Code: http.StatusOK},
   394  			}...)
   395  		})
   396  
   397  		t.Run("ignore-case in api level", func(t *testing.T) {
   398  			globalConf := config.Global()
   399  			globalConf.IgnoreEndpointCase = false
   400  			config.SetGlobal(globalConf)
   401  
   402  			v := spec.VersionData.Versions["v1"]
   403  			v.IgnoreEndpointCase = true
   404  			spec.VersionData.Versions["v1"] = v
   405  
   406  			LoadAPI(spec)
   407  
   408  			_, _ = ts.Run(t, []test.TestCase{
   409  				{Path: "/foo", Code: http.StatusOK},
   410  				{Path: "/Foo", Code: http.StatusOK},
   411  				{Path: "/bar", Code: http.StatusOK},
   412  				{Path: "/Bar", Code: http.StatusOK},
   413  			}...)
   414  		})
   415  
   416  		// Check whether everything returns normal
   417  		globalConf := config.Global()
   418  		globalConf.IgnoreEndpointCase = false
   419  		config.SetGlobal(globalConf)
   420  
   421  		v := spec.VersionData.Versions["v1"]
   422  		v.IgnoreEndpointCase = false
   423  		spec.VersionData.Versions["v1"] = v
   424  
   425  		LoadAPI(spec)
   426  
   427  		_, _ = ts.Run(t, []test.TestCase{
   428  			{Path: "/foo", Code: http.StatusUnauthorized},
   429  			{Path: "/Foo", Code: http.StatusOK},
   430  			{Path: "/bar", Code: http.StatusOK},
   431  			{Path: "/Bar", Code: http.StatusOK},
   432  		}...)
   433  	})
   434  }
   435  
   436  func TestWhitelistMethodWithAdditionalMiddleware(t *testing.T) {
   437  	ts := StartTest()
   438  	defer ts.Close()
   439  
   440  	t.Run("Extended Paths", func(t *testing.T) {
   441  		BuildAndLoadAPI(func(spec *APISpec) {
   442  			spec.UseKeylessAccess = true
   443  			spec.Proxy.ListenPath = "/"
   444  
   445  			UpdateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
   446  				v.UseExtendedPaths = true
   447  
   448  				json.Unmarshal([]byte(`[
   449  					{
   450  						"path": "/get",
   451  						"method_actions": {"GET": {"action": "no_action"}}
   452  					}
   453  				]`), &v.ExtendedPaths.WhiteList)
   454  				json.Unmarshal([]byte(`[
   455  					{
   456  						"add_headers": {"foo": "bar"},
   457  						"path": "/get",
   458  						"method": "GET",
   459  						"act_on": false
   460  					}
   461  				]`), &v.ExtendedPaths.TransformResponseHeader)
   462  			})
   463  			spec.ResponseProcessors = []apidef.ResponseProcessor{{Name: "header_injector"}}
   464  
   465  		})
   466  
   467  		//headers := map[string]string{"foo": "bar"}
   468  		ts.Run(t, []test.TestCase{
   469  			//Should get original upstream response
   470  			//{Method: "GET", Path: "/get", Code: http.StatusOK, HeadersMatch: headers},
   471  			//Reject not whitelisted (but know by upstream) path
   472  			{Method: "POST", Path: "/get", Code: http.StatusForbidden},
   473  		}...)
   474  	})
   475  }
   476  
   477  func TestSyncAPISpecsDashboardSuccess(t *testing.T) {
   478  	ReloadTestCase.Enable()
   479  	defer ReloadTestCase.Disable()
   480  	// Test Dashboard
   481  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   482  		if r.URL.Path == "/system/apis" {
   483  			w.Write([]byte(`{"Status": "OK", "Nonce": "1", "Message": [{"api_definition": {}}]}`))
   484  		} else {
   485  			t.Fatal("Unknown dashboard API request", r)
   486  		}
   487  	}))
   488  	defer ts.Close()
   489  
   490  	apisMu.Lock()
   491  	apisByID = make(map[string]*APISpec)
   492  	apisMu.Unlock()
   493  
   494  	globalConf := config.Global()
   495  	globalConf.UseDBAppConfigs = true
   496  	globalConf.AllowInsecureConfigs = true
   497  	globalConf.DBAppConfOptions.ConnectionString = ts.URL
   498  	config.SetGlobal(globalConf)
   499  
   500  	defer ResetTestConfig()
   501  
   502  	var wg sync.WaitGroup
   503  	wg.Add(1)
   504  	msg := redis.Message{Payload: `{"Command": "ApiUpdated"}`}
   505  	handled := func(got NotificationCommand) {
   506  		if want := NoticeApiUpdated; got != want {
   507  			t.Fatalf("want %q, got %q", want, got)
   508  		}
   509  	}
   510  	handleRedisEvent(&msg, handled, wg.Done)
   511  
   512  	ReloadTestCase.TickOk(t)
   513  
   514  	// Wait for the reload to finish, then check it worked
   515  	wg.Wait()
   516  	apisMu.RLock()
   517  	if len(apisByID) != 1 {
   518  		t.Error("Should return array with one spec", apisByID)
   519  	}
   520  	apisMu.RUnlock()
   521  }
   522  
   523  func TestRoundRobin(t *testing.T) {
   524  	rr := RoundRobin{}
   525  	for _, want := range []int{0, 1, 2, 0} {
   526  		if got := rr.WithLen(3); got != want {
   527  			t.Errorf("RR Pos wrong: want %d got %d", want, got)
   528  		}
   529  	}
   530  	if got, want := rr.WithLen(0), 0; got != want {
   531  		t.Errorf("RR Pos of 0 wrong: want %d got %d", want, got)
   532  	}
   533  }
   534  
   535  func setupKeepalive(conn net.Conn) error {
   536  	tcpConn := conn.(*net.TCPConn)
   537  	if err := tcpConn.SetKeepAlive(true); err != nil {
   538  		return err
   539  	}
   540  	if err := tcpConn.SetKeepAlivePeriod(30 * time.Second); err != nil {
   541  		return err
   542  	}
   543  	return nil
   544  }
   545  
   546  type customListener struct {
   547  	L net.Listener
   548  }
   549  
   550  func (ln *customListener) Init(addr string) (err error) {
   551  	ln.L, err = net.Listen("tcp", addr)
   552  	return
   553  }
   554  
   555  func (ln *customListener) Accept() (conn net.Conn, err error) {
   556  	c, err := ln.L.Accept()
   557  	if err != nil {
   558  		return
   559  	}
   560  
   561  	if err = setupKeepalive(c); err != nil {
   562  		c.Close()
   563  		return
   564  	}
   565  
   566  	handshake := make([]byte, 6)
   567  	if _, err = io.ReadFull(c, handshake); err != nil {
   568  		return
   569  	}
   570  
   571  	idLenBuf := make([]byte, 1)
   572  	if _, err = io.ReadFull(c, idLenBuf); err != nil {
   573  		return
   574  	}
   575  
   576  	idLen := uint8(idLenBuf[0])
   577  	id := make([]byte, idLen)
   578  	if _, err = io.ReadFull(c, id); err != nil {
   579  		return
   580  	}
   581  
   582  	return c, nil
   583  }
   584  
   585  func (ln *customListener) Close() error {
   586  	return ln.L.Close()
   587  }
   588  
   589  func TestDefaultVersion(t *testing.T) {
   590  	ts := StartTest()
   591  	defer ts.Close()
   592  
   593  	key := testPrepareDefaultVersion()
   594  
   595  	authHeaders := map[string]string{"authorization": key}
   596  
   597  	ts.Run(t, []test.TestCase{
   598  		{Path: "/foo", Headers: authHeaders, Code: http.StatusForbidden},      // Not whitelisted for default v2
   599  		{Path: "/bar", Headers: authHeaders, Code: http.StatusOK},             // Whitelisted for default v2
   600  		{Path: "/foo?v=v1", Headers: authHeaders, Code: http.StatusOK},        // Allowed for v1
   601  		{Path: "/bar?v=v1", Headers: authHeaders, Code: http.StatusForbidden}, // Not allowed for v1
   602  	}...)
   603  }
   604  
   605  func BenchmarkDefaultVersion(b *testing.B) {
   606  	b.ReportAllocs()
   607  
   608  	ts := StartTest()
   609  	defer ts.Close()
   610  
   611  	key := testPrepareDefaultVersion()
   612  
   613  	authHeaders := map[string]string{"authorization": key}
   614  
   615  	for i := 0; i < b.N; i++ {
   616  		ts.Run(
   617  			b,
   618  			[]test.TestCase{
   619  				{Path: "/foo", Headers: authHeaders, Code: http.StatusForbidden},      // Not whitelisted for default v2
   620  				{Path: "/bar", Headers: authHeaders, Code: http.StatusOK},             // Whitelisted for default v2
   621  				{Path: "/foo?v=v1", Headers: authHeaders, Code: http.StatusOK},        // Allowed for v1
   622  				{Path: "/bar?v=v1", Headers: authHeaders, Code: http.StatusForbidden}, // Not allowed for v1
   623  			}...,
   624  		)
   625  	}
   626  }
   627  
   628  func testPrepareDefaultVersion() string {
   629  	BuildAndLoadAPI(func(spec *APISpec) {
   630  		v1 := apidef.VersionInfo{Name: "v1"}
   631  		v1.Name = "v1"
   632  		v1.Paths.WhiteList = []string{"/foo"}
   633  
   634  		v2 := apidef.VersionInfo{Name: "v2"}
   635  		v2.Paths.WhiteList = []string{"/bar"}
   636  
   637  		spec.VersionDefinition.Location = urlParamLocation
   638  		spec.VersionDefinition.Key = "v"
   639  		spec.VersionData.NotVersioned = false
   640  
   641  		spec.VersionData.Versions["v1"] = v1
   642  		spec.VersionData.Versions["v2"] = v2
   643  		spec.VersionData.DefaultVersion = "v2"
   644  		spec.Proxy.ListenPath = "/"
   645  
   646  		spec.UseKeylessAccess = false
   647  	})
   648  
   649  	return CreateSession(func(s *user.SessionState) {
   650  		s.AccessRights = map[string]user.AccessDefinition{"test": {
   651  			APIID: "test", Versions: []string{"v1", "v2"},
   652  		}}
   653  		s.Mutex = &sync.RWMutex{}
   654  	})
   655  }
   656  
   657  func TestGetVersionFromRequest(t *testing.T) {
   658  	ts := StartTest()
   659  	defer ts.Close()
   660  
   661  	versionInfo := apidef.VersionInfo{}
   662  	versionInfo.Paths.WhiteList = []string{"/foo"}
   663  	versionInfo.Paths.BlackList = []string{"/bar"}
   664  
   665  	t.Run("Header location", func(t *testing.T) {
   666  		BuildAndLoadAPI(func(spec *APISpec) {
   667  			spec.Proxy.ListenPath = "/"
   668  			spec.VersionData.NotVersioned = false
   669  			spec.VersionDefinition.Location = headerLocation
   670  			spec.VersionDefinition.Key = "X-API-Version"
   671  			spec.VersionData.Versions["v1"] = versionInfo
   672  		})
   673  
   674  		headers := map[string]string{"X-API-Version": "v1"}
   675  
   676  		ts.Run(t, []test.TestCase{
   677  			{Path: "/foo", Code: http.StatusOK, Headers: headers},
   678  			{Path: "/bar", Code: http.StatusForbidden, Headers: headers},
   679  		}...)
   680  	})
   681  
   682  	t.Run("URL param location", func(t *testing.T) {
   683  		BuildAndLoadAPI(func(spec *APISpec) {
   684  			spec.Proxy.ListenPath = "/"
   685  			spec.VersionData.NotVersioned = false
   686  			spec.VersionDefinition.Location = urlParamLocation
   687  			spec.VersionDefinition.Key = "version"
   688  			spec.VersionData.Versions["v2"] = versionInfo
   689  		})
   690  
   691  		ts.Run(t, []test.TestCase{
   692  			{Path: "/foo?version=v2", Code: http.StatusOK},
   693  			{Path: "/bar?version=v2", Code: http.StatusForbidden},
   694  		}...)
   695  	})
   696  
   697  	t.Run("URL location", func(t *testing.T) {
   698  		BuildAndLoadAPI(func(spec *APISpec) {
   699  			spec.Proxy.ListenPath = "/"
   700  			spec.VersionData.NotVersioned = false
   701  			spec.VersionDefinition.Location = urlLocation
   702  			spec.VersionData.Versions["v3"] = versionInfo
   703  		})
   704  
   705  		ts.Run(t, []test.TestCase{
   706  			{Path: "/v3/foo", Code: http.StatusOK},
   707  			{Path: "/v3/bar", Code: http.StatusForbidden},
   708  		}...)
   709  	})
   710  }
   711  
   712  func BenchmarkGetVersionFromRequest(b *testing.B) {
   713  	b.ReportAllocs()
   714  	ts := StartTest()
   715  	defer ts.Close()
   716  
   717  	versionInfo := apidef.VersionInfo{}
   718  	versionInfo.Paths.WhiteList = []string{"/foo"}
   719  	versionInfo.Paths.BlackList = []string{"/bar"}
   720  
   721  	b.Run("Header location", func(b *testing.B) {
   722  		b.ReportAllocs()
   723  		BuildAndLoadAPI(func(spec *APISpec) {
   724  			spec.Proxy.ListenPath = "/"
   725  			spec.VersionData.NotVersioned = false
   726  			spec.VersionDefinition.Location = headerLocation
   727  			spec.VersionDefinition.Key = "X-API-Version"
   728  			spec.VersionData.Versions["v1"] = versionInfo
   729  		})
   730  
   731  		headers := map[string]string{"X-API-Version": "v1"}
   732  
   733  		for i := 0; i < b.N; i++ {
   734  			ts.Run(b, []test.TestCase{
   735  				{Path: "/foo", Code: http.StatusOK, Headers: headers},
   736  				{Path: "/bar", Code: http.StatusForbidden, Headers: headers},
   737  			}...)
   738  		}
   739  	})
   740  
   741  	b.Run("URL param location", func(b *testing.B) {
   742  		b.ReportAllocs()
   743  		BuildAndLoadAPI(func(spec *APISpec) {
   744  			spec.Proxy.ListenPath = "/"
   745  			spec.VersionData.NotVersioned = false
   746  			spec.VersionDefinition.Location = urlParamLocation
   747  			spec.VersionDefinition.Key = "version"
   748  			spec.VersionData.Versions["v2"] = versionInfo
   749  		})
   750  
   751  		for i := 0; i < b.N; i++ {
   752  			ts.Run(b, []test.TestCase{
   753  				{Path: "/foo?version=v2", Code: http.StatusOK},
   754  				{Path: "/bar?version=v2", Code: http.StatusForbidden},
   755  			}...)
   756  		}
   757  	})
   758  
   759  	b.Run("URL location", func(b *testing.B) {
   760  		b.ReportAllocs()
   761  		BuildAndLoadAPI(func(spec *APISpec) {
   762  			spec.Proxy.ListenPath = "/"
   763  			spec.VersionData.NotVersioned = false
   764  			spec.VersionDefinition.Location = urlLocation
   765  			spec.VersionData.Versions["v3"] = versionInfo
   766  		})
   767  
   768  		for i := 0; i < b.N; i++ {
   769  			ts.Run(b, []test.TestCase{
   770  				{Path: "/v3/foo", Code: http.StatusOK},
   771  				{Path: "/v3/bar", Code: http.StatusForbidden},
   772  			}...)
   773  		}
   774  	})
   775  }
   776  
   777  func TestSyncAPISpecsDashboardJSONFailure(t *testing.T) {
   778  	ReloadTestCase.Enable()
   779  	defer ReloadTestCase.Disable()
   780  	// Test Dashboard
   781  	callNum := 0
   782  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   783  		if r.URL.Path == "/system/apis" {
   784  			if callNum == 0 {
   785  				w.Write([]byte(`{"Status": "OK", "Nonce": "1", "Message": [{"api_definition": {}}]}`))
   786  			} else {
   787  				w.Write([]byte(`{"Status": "OK", "Nonce": "1", "Message": "this is a string"`))
   788  			}
   789  
   790  			callNum += 1
   791  		} else {
   792  			t.Fatal("Unknown dashboard API request", r)
   793  		}
   794  	}))
   795  	defer ts.Close()
   796  
   797  	apisMu.Lock()
   798  	apisByID = make(map[string]*APISpec)
   799  	apisMu.Unlock()
   800  
   801  	globalConf := config.Global()
   802  	globalConf.UseDBAppConfigs = true
   803  	globalConf.AllowInsecureConfigs = true
   804  	globalConf.DBAppConfOptions.ConnectionString = ts.URL
   805  	config.SetGlobal(globalConf)
   806  
   807  	defer ResetTestConfig()
   808  
   809  	var wg sync.WaitGroup
   810  	wg.Add(1)
   811  	msg := redis.Message{Payload: `{"Command": "ApiUpdated"}`}
   812  	handled := func(got NotificationCommand) {
   813  		if want := NoticeApiUpdated; got != want {
   814  			t.Fatalf("want %q, got %q", want, got)
   815  		}
   816  	}
   817  	handleRedisEvent(&msg, handled, wg.Done)
   818  
   819  	ReloadTestCase.TickOk(t)
   820  
   821  	// Wait for the reload to finish, then check it worked
   822  	wg.Wait()
   823  	apisMu.RLock()
   824  	if len(apisByID) != 1 {
   825  		t.Error("should return array with one spec", apisByID)
   826  	}
   827  	apisMu.RUnlock()
   828  
   829  	// Second call
   830  
   831  	var wg2 sync.WaitGroup
   832  	wg2.Add(1)
   833  	ReloadTestCase.Reset()
   834  	handleRedisEvent(&msg, handled, wg2.Done)
   835  
   836  	ReloadTestCase.TickOk(t)
   837  	// Wait for the reload to finish, then check it worked
   838  	wg2.Wait()
   839  	apisMu.RLock()
   840  	if len(apisByID) != 1 {
   841  		t.Error("second call should return array with one spec", apisByID)
   842  	}
   843  	apisMu.RUnlock()
   844  
   845  }