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 }