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

     1  package gateway
     2  
     3  import (
     4  	"crypto/md5"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/TykTechnologies/tyk/apidef"
    12  	"github.com/TykTechnologies/tyk/storage"
    13  )
    14  
    15  const (
    16  	extractorValueInput = "testkey"
    17  
    18  	extractorRegexExpr       = "prefix-(.*)"
    19  	extractorRegexInput      = "prefix-testkey123"
    20  	extractorRegexMatchIndex = 0
    21  
    22  	extractorXPathExpr  = "//object/key"
    23  	extractorXPathInput = "<object><key>thevalue</key></object>"
    24  
    25  	extractorHeaderName = "testheader"
    26  	extractorParamName  = "testparam"
    27  )
    28  
    29  func createSpecTestFrom(t testing.TB, def *apidef.APIDefinition) *APISpec {
    30  	loader := APIDefinitionLoader{}
    31  	spec := loader.MakeSpec(def, nil)
    32  	tname := t.Name()
    33  	redisStore := &storage.RedisCluster{KeyPrefix: tname + "-apikey."}
    34  	healthStore := &storage.RedisCluster{KeyPrefix: tname + "-apihealth."}
    35  	orgStore := &storage.RedisCluster{KeyPrefix: tname + "-orgKey."}
    36  	spec.Init(redisStore, redisStore, healthStore, orgStore)
    37  	return spec
    38  }
    39  
    40  func prepareExtractor(t testing.TB, extractorSource apidef.IdExtractorSource, extractorType apidef.IdExtractorType, config map[string]interface{}) (IdExtractor, *APISpec) {
    41  	def := &apidef.APIDefinition{
    42  		OrgID: MockOrgID,
    43  		CustomMiddleware: apidef.MiddlewareSection{
    44  			IdExtractor: apidef.MiddlewareIdExtractor{
    45  				ExtractFrom:     extractorSource,
    46  				ExtractWith:     extractorType,
    47  				ExtractorConfig: config,
    48  			},
    49  		},
    50  	}
    51  	spec := createSpecTestFrom(t, def)
    52  	mw := BaseMiddleware{Spec: spec}
    53  	newExtractor(spec, mw)
    54  	return spec.CustomMiddleware.IdExtractor.Extractor.(IdExtractor), spec
    55  }
    56  
    57  func prepareHeaderExtractorRequest(headers map[string]string) *http.Request {
    58  	r, _ := http.NewRequest(http.MethodGet, "/", nil)
    59  	if headers == nil {
    60  		return r
    61  	}
    62  	for k, v := range headers {
    63  		r.Header.Add(k, v)
    64  	}
    65  	return r
    66  }
    67  
    68  func prepareBodyExtractorRequest(input string) *http.Request {
    69  	r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(input))
    70  	return r
    71  }
    72  
    73  func prepareExtractorFormRequest(values map[string]string) *http.Request {
    74  	formData := url.Values{}
    75  	if values != nil {
    76  		for k, v := range values {
    77  			formData.Set(k, v)
    78  		}
    79  	}
    80  	r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(formData.Encode()))
    81  	r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    82  	return r
    83  }
    84  
    85  func generateSessionID(input string) string {
    86  	data := []byte(input)
    87  	tokenID := fmt.Sprintf("%x", md5.Sum(data))
    88  	return generateToken(MockOrgID, tokenID)
    89  }
    90  
    91  func TestValueExtractor(t *testing.T) {
    92  	testSessionID := generateSessionID(extractorValueInput)
    93  
    94  	t.Run("HeaderSource", func(t *testing.T) {
    95  		extractor, spec := prepareExtractor(t, apidef.HeaderSource, apidef.ValueExtractor, map[string]interface{}{
    96  			"header_name": extractorHeaderName,
    97  		})
    98  
    99  		r := prepareHeaderExtractorRequest(nil)
   100  		sessionID, overrides := extractor.ExtractAndCheck(r)
   101  		if sessionID != "" {
   102  			t.Fatalf("should return an empty session ID, got %s", sessionID)
   103  		}
   104  		if overrides.ResponseCode != 400 {
   105  			t.Fatalf("should return 400, got %d", overrides.ResponseCode)
   106  		}
   107  
   108  		r = prepareHeaderExtractorRequest(map[string]string{extractorHeaderName: extractorValueInput})
   109  		sessionID, overrides = extractor.ExtractAndCheck(r)
   110  		if sessionID != testSessionID {
   111  			t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
   112  		}
   113  		if storage.TokenOrg(sessionID) != spec.OrgID {
   114  			t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
   115  		}
   116  		if overrides.ResponseCode != 0 {
   117  			t.Fatalf("response code should be 0, got %d", overrides.ResponseCode)
   118  		}
   119  	})
   120  
   121  	t.Run("FormSource", func(t *testing.T) {
   122  		extractor, spec := prepareExtractor(t, apidef.FormSource, apidef.ValueExtractor, map[string]interface{}{
   123  			"param_name": extractorParamName,
   124  		})
   125  
   126  		r := prepareExtractorFormRequest(nil)
   127  		sessionID, overrides := extractor.ExtractAndCheck(r)
   128  		if sessionID != "" {
   129  			t.Fatalf("should return an empty session ID, got %s", sessionID)
   130  		}
   131  		if overrides.ResponseCode != 400 {
   132  			t.Fatalf("should return 400, got %d", overrides.ResponseCode)
   133  		}
   134  
   135  		r = prepareExtractorFormRequest(map[string]string{extractorParamName: extractorValueInput})
   136  		sessionID, overrides = extractor.ExtractAndCheck(r)
   137  		if sessionID != testSessionID {
   138  			t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
   139  		}
   140  		if storage.TokenOrg(sessionID) != spec.OrgID {
   141  			t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
   142  		}
   143  		if overrides.ResponseCode != 0 {
   144  			t.Fatalf("response code should be 0, got %d", overrides.ResponseCode)
   145  		}
   146  	})
   147  }
   148  
   149  func TestRegexExtractor(t *testing.T) {
   150  	testSessionID := generateSessionID(extractorRegexInput)
   151  
   152  	t.Run("HeaderSource", func(t *testing.T) {
   153  		extractor, spec := prepareExtractor(t, apidef.HeaderSource, apidef.RegexExtractor, map[string]interface{}{
   154  			"regex_expression":  extractorRegexExpr,
   155  			"regex_match_index": extractorRegexMatchIndex,
   156  			"header_name":       extractorHeaderName,
   157  		})
   158  
   159  		r := prepareHeaderExtractorRequest(nil)
   160  		sessionID, overrides := extractor.ExtractAndCheck(r)
   161  		if sessionID != "" {
   162  			t.Fatalf("should return an empty session ID, got %s", sessionID)
   163  		}
   164  		if overrides.ResponseCode != 400 {
   165  			t.Fatalf("should return 400, got %d", overrides.ResponseCode)
   166  		}
   167  
   168  		r = prepareHeaderExtractorRequest(map[string]string{extractorHeaderName: extractorRegexInput})
   169  		sessionID, overrides = extractor.ExtractAndCheck(r)
   170  		if sessionID != testSessionID {
   171  			t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
   172  		}
   173  		if storage.TokenOrg(sessionID) != spec.OrgID {
   174  			t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
   175  		}
   176  		if overrides.ResponseCode != 0 {
   177  			t.Fatalf("response code should be 0, got %d", overrides.ResponseCode)
   178  		}
   179  	})
   180  
   181  	t.Run("BodySource", func(t *testing.T) {
   182  		extractor, spec := prepareExtractor(t, apidef.BodySource, apidef.RegexExtractor, map[string]interface{}{
   183  			"regex_expression":  extractorRegexExpr,
   184  			"regex_match_index": extractorRegexMatchIndex,
   185  		})
   186  
   187  		r := prepareBodyExtractorRequest("")
   188  		sessionID, overrides := extractor.ExtractAndCheck(r)
   189  		if sessionID != "" {
   190  			t.Fatalf("should return an empty session ID, got %s", sessionID)
   191  		}
   192  		if overrides.ResponseCode != 400 {
   193  			t.Fatalf("should return 400, got %d", overrides.ResponseCode)
   194  		}
   195  
   196  		r = prepareBodyExtractorRequest(extractorRegexInput)
   197  		sessionID, overrides = extractor.ExtractAndCheck(r)
   198  		if sessionID != testSessionID {
   199  			t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
   200  		}
   201  		if storage.TokenOrg(sessionID) != spec.OrgID {
   202  			t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
   203  		}
   204  		if overrides.ResponseCode != 0 {
   205  			t.Fatalf("response code should be 0, got %d", overrides.ResponseCode)
   206  		}
   207  	})
   208  
   209  	t.Run("FormSource", func(t *testing.T) {
   210  		extractor, spec := prepareExtractor(t, apidef.FormSource, apidef.RegexExtractor, map[string]interface{}{
   211  			"regex_expression":  extractorRegexExpr,
   212  			"regex_match_index": extractorRegexMatchIndex,
   213  			"param_name":        extractorParamName,
   214  		})
   215  
   216  		r := prepareExtractorFormRequest(nil)
   217  		sessionID, overrides := extractor.ExtractAndCheck(r)
   218  		if sessionID != "" {
   219  			t.Fatalf("should return an empty session ID, got %s", sessionID)
   220  		}
   221  		if overrides.ResponseCode != 400 {
   222  			t.Fatalf("should return 400, got %d", overrides.ResponseCode)
   223  		}
   224  
   225  		r = prepareExtractorFormRequest(map[string]string{extractorParamName: extractorRegexInput})
   226  		sessionID, overrides = extractor.ExtractAndCheck(r)
   227  		if sessionID != testSessionID {
   228  			t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
   229  		}
   230  		if storage.TokenOrg(sessionID) != spec.OrgID {
   231  			t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
   232  		}
   233  		if overrides.ResponseCode != 0 {
   234  			t.Fatalf("response code should be 0, got %d", overrides.ResponseCode)
   235  		}
   236  	})
   237  }
   238  
   239  func TestXPathExtractor(t *testing.T) {
   240  	testSessionID := generateSessionID("thevalue")
   241  
   242  	t.Run("HeaderSource", func(t *testing.T) {
   243  		extractor, spec := prepareExtractor(t, apidef.HeaderSource, apidef.XPathExtractor, map[string]interface{}{
   244  			"xpath_expression": extractorXPathExpr,
   245  			"header_name":      extractorHeaderName,
   246  		})
   247  
   248  		r := prepareHeaderExtractorRequest(nil)
   249  		sessionID, overrides := extractor.ExtractAndCheck(r)
   250  		if sessionID != "" {
   251  			t.Fatalf("should return an empty session ID, got %s", sessionID)
   252  		}
   253  		if overrides.ResponseCode != 400 {
   254  			t.Fatalf("should return 400, got %d", overrides.ResponseCode)
   255  		}
   256  
   257  		r = prepareHeaderExtractorRequest(map[string]string{extractorHeaderName: extractorXPathInput})
   258  		sessionID, overrides = extractor.ExtractAndCheck(r)
   259  		if sessionID != testSessionID {
   260  			t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
   261  		}
   262  		if storage.TokenOrg(sessionID) != spec.OrgID {
   263  			t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
   264  		}
   265  		if overrides.ResponseCode != 0 {
   266  			t.Fatalf("response code should be 0, got %d", overrides.ResponseCode)
   267  		}
   268  	})
   269  
   270  	t.Run("BodySource", func(t *testing.T) {
   271  		extractor, spec := prepareExtractor(t, apidef.BodySource, apidef.XPathExtractor, map[string]interface{}{
   272  			"xpath_expression": extractorXPathExpr,
   273  		})
   274  
   275  		r := prepareBodyExtractorRequest("")
   276  		sessionID, overrides := extractor.ExtractAndCheck(r)
   277  		if sessionID != "" {
   278  			t.Fatalf("should return an empty session ID, got %s", sessionID)
   279  		}
   280  		if overrides.ResponseCode != 400 {
   281  			t.Fatalf("should return 400, got %d", overrides.ResponseCode)
   282  		}
   283  
   284  		r = prepareBodyExtractorRequest(extractorXPathInput)
   285  		sessionID, overrides = extractor.ExtractAndCheck(r)
   286  		if sessionID != testSessionID {
   287  			t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
   288  		}
   289  		if storage.TokenOrg(sessionID) != spec.OrgID {
   290  			t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
   291  		}
   292  		if overrides.ResponseCode != 0 {
   293  			t.Fatalf("response code should be 0, got %d", overrides.ResponseCode)
   294  		}
   295  	})
   296  
   297  	t.Run("FormSource", func(t *testing.T) {
   298  		extractor, spec := prepareExtractor(t, apidef.FormSource, apidef.XPathExtractor, map[string]interface{}{
   299  			"xpath_expression": extractorXPathExpr,
   300  			"param_name":       extractorParamName,
   301  		})
   302  
   303  		r := prepareExtractorFormRequest(nil)
   304  		sessionID, overrides := extractor.ExtractAndCheck(r)
   305  		if sessionID != "" {
   306  			t.Fatalf("should return an empty session ID, got %s", sessionID)
   307  		}
   308  		if overrides.ResponseCode != 400 {
   309  			t.Fatalf("should return 400, got %d", overrides.ResponseCode)
   310  		}
   311  
   312  		r = prepareExtractorFormRequest(map[string]string{extractorParamName: extractorXPathInput})
   313  		sessionID, overrides = extractor.ExtractAndCheck(r)
   314  		if sessionID != testSessionID {
   315  			t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
   316  		}
   317  		if storage.TokenOrg(sessionID) != spec.OrgID {
   318  			t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
   319  		}
   320  		if overrides.ResponseCode != 0 {
   321  			t.Fatalf("response code should be 0, got %d", overrides.ResponseCode)
   322  		}
   323  	})
   324  }
   325  
   326  func BenchmarkValueExtractor(b *testing.B) {
   327  	b.Run("HeaderSource", func(b *testing.B) {
   328  		b.ReportAllocs()
   329  		extractor, _ := prepareExtractor(b, apidef.HeaderSource, apidef.ValueExtractor, map[string]interface{}{
   330  			"header_name": extractorHeaderName,
   331  		})
   332  		headers := map[string]string{extractorHeaderName: extractorValueInput}
   333  		for i := 0; i < b.N; i++ {
   334  			r := prepareHeaderExtractorRequest(headers)
   335  			extractor.ExtractAndCheck(r)
   336  		}
   337  	})
   338  	b.Run("FormSource", func(b *testing.B) {
   339  		b.ReportAllocs()
   340  		extractor, _ := prepareExtractor(b, apidef.FormSource, apidef.ValueExtractor, map[string]interface{}{
   341  			"param_name": extractorParamName,
   342  		})
   343  		params := map[string]string{extractorParamName: extractorValueInput}
   344  		for i := 0; i < b.N; i++ {
   345  			r := prepareExtractorFormRequest(params)
   346  			extractor.ExtractAndCheck(r)
   347  		}
   348  	})
   349  }
   350  
   351  func BenchmarkRegexExtractor(b *testing.B) {
   352  	b.Run("HeaderSource", func(b *testing.B) {
   353  		b.ReportAllocs()
   354  		headerName := "testheader"
   355  		extractor, _ := prepareExtractor(b, apidef.HeaderSource, apidef.RegexExtractor, map[string]interface{}{
   356  			"regex_expression":  extractorRegexExpr,
   357  			"regex_match_index": extractorRegexMatchIndex,
   358  			"header_name":       headerName,
   359  		})
   360  		headers := map[string]string{extractorHeaderName: extractorRegexInput}
   361  		for i := 0; i < b.N; i++ {
   362  			r := prepareHeaderExtractorRequest(headers)
   363  			extractor.ExtractAndCheck(r)
   364  		}
   365  	})
   366  	b.Run("BodySource", func(b *testing.B) {
   367  		b.ReportAllocs()
   368  		extractor, _ := prepareExtractor(b, apidef.BodySource, apidef.RegexExtractor, map[string]interface{}{
   369  			"regex_expression":  extractorRegexExpr,
   370  			"regex_match_index": extractorRegexMatchIndex,
   371  		})
   372  		for i := 0; i < b.N; i++ {
   373  			r := prepareBodyExtractorRequest(extractorRegexInput)
   374  			extractor.ExtractAndCheck(r)
   375  		}
   376  	})
   377  	b.Run("FormSource", func(b *testing.B) {
   378  		b.ReportAllocs()
   379  		extractor, _ := prepareExtractor(b, apidef.FormSource, apidef.RegexExtractor, map[string]interface{}{
   380  			"regex_expression":  extractorRegexExpr,
   381  			"regex_match_index": extractorRegexMatchIndex,
   382  			"param_name":        extractorParamName,
   383  		})
   384  		params := map[string]string{extractorParamName: extractorRegexInput}
   385  		for i := 0; i < b.N; i++ {
   386  			r := prepareExtractorFormRequest(params)
   387  			extractor.ExtractAndCheck(r)
   388  		}
   389  	})
   390  }
   391  
   392  func BenchmarkXPathExtractor(b *testing.B) {
   393  	b.Run("HeaderSource", func(b *testing.B) {
   394  		b.ReportAllocs()
   395  		extractor, _ := prepareExtractor(b, apidef.HeaderSource, apidef.XPathExtractor, map[string]interface{}{
   396  			"xpath_expression": extractorXPathExpr,
   397  			"header_name":      extractorHeaderName,
   398  		})
   399  		headers := map[string]string{extractorHeaderName: extractorXPathInput}
   400  		for i := 0; i < b.N; i++ {
   401  			r := prepareHeaderExtractorRequest(headers)
   402  			extractor.ExtractAndCheck(r)
   403  		}
   404  	})
   405  	b.Run("BodySource", func(b *testing.B) {
   406  		b.ReportAllocs()
   407  		extractor, _ := prepareExtractor(b, apidef.BodySource, apidef.XPathExtractor, map[string]interface{}{
   408  			"xpath_expression": extractorXPathExpr,
   409  		})
   410  		for i := 0; i < b.N; i++ {
   411  			r := prepareBodyExtractorRequest(extractorXPathInput)
   412  			extractor.ExtractAndCheck(r)
   413  		}
   414  	})
   415  	b.Run("FormSource", func(b *testing.B) {
   416  		b.ReportAllocs()
   417  		extractor, _ := prepareExtractor(b, apidef.FormSource, apidef.XPathExtractor, map[string]interface{}{
   418  			"xpath_expression": extractorXPathExpr,
   419  			"param_name":       extractorParamName,
   420  		})
   421  		params := map[string]string{extractorParamName: extractorXPathInput}
   422  		for i := 0; i < b.N; i++ {
   423  			r := prepareExtractorFormRequest(params)
   424  			extractor.ExtractAndCheck(r)
   425  		}
   426  	})
   427  }