github.com/craicoverflow/tyk@v2.9.6-rc3+incompatible/coprocess/python/coprocess_python_test.go (about) 1 package python 2 3 import ( 4 "bytes" 5 "context" 6 "mime/multipart" 7 "net/http" 8 "os" 9 "path/filepath" 10 "runtime" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/TykTechnologies/tyk/apidef" 16 "github.com/TykTechnologies/tyk/config" 17 "github.com/TykTechnologies/tyk/gateway" 18 "github.com/TykTechnologies/tyk/test" 19 "github.com/TykTechnologies/tyk/user" 20 ) 21 22 var pkgPath string 23 24 func init() { 25 _, filename, _, _ := runtime.Caller(0) 26 pkgPath = filepath.Dir(filename) + "./../../" 27 } 28 29 var pythonBundleWithAuthCheck = map[string]string{ 30 "manifest.json": ` 31 { 32 "file_list": [ 33 "middleware.py" 34 ], 35 "custom_middleware": { 36 "driver": "python", 37 "auth_check": { 38 "name": "MyAuthHook" 39 } 40 } 41 } 42 `, 43 "middleware.py": ` 44 from tyk.decorators import * 45 from gateway import TykGateway as tyk 46 47 @Hook 48 def MyAuthHook(request, session, metadata, spec): 49 auth_header = request.get_header('Authorization') 50 if auth_header == 'valid_token': 51 session.rate = 1000.0 52 session.per = 1.0 53 session.quota_max = 1 54 session.quota_renewal_rate = 60 55 metadata["token"] = "valid_token" 56 if auth_header == '47a0c79c427728b3df4af62b9228c8ae11': 57 policy_id = request.get_header('Policy') 58 session.apply_policy_id = policy_id 59 metadata["token"] = "47a0c79c427728b3df4af62b9228c8ae11" 60 return request, session, metadata 61 `, 62 } 63 64 var pythonBundleWithPostHook = map[string]string{ 65 "manifest.json": ` 66 { 67 "file_list": [ 68 "middleware.py" 69 ], 70 "custom_middleware": { 71 "driver": "python", 72 "post": [{ 73 "name": "MyPostHook" 74 }] 75 } 76 } 77 `, 78 "middleware.py": ` 79 from tyk.decorators import * 80 from gateway import TykGateway as tyk 81 import json 82 83 @Hook 84 def MyPostHook(request, session, spec): 85 if "testkey" not in session.metadata.keys(): 86 request.object.return_overrides.response_code = 400 87 request.object.return_overrides.response_error = "'testkey' not found in metadata" 88 return request, session 89 nested_data = json.loads(session.metadata["testkey"]) 90 if "nestedkey" not in nested_data: 91 request.object.return_overrides.response_code = 400 92 request.object.return_overrides.response_error = "'nestedkey' not found in nested metadata" 93 return request, session 94 if "stringkey" not in session.metadata.keys(): 95 request.object.return_overrides.response_code = 400 96 request.object.return_overrides.response_error = "'stringkey' not found in metadata" 97 return request, session 98 stringkey = session.metadata["stringkey"] 99 if stringkey != "testvalue": 100 request.object.return_overrides.response_code = 400 101 request.object.return_overrides.response_error = "'stringkey' value doesn't match" 102 return request, session 103 return request, session 104 105 `, 106 } 107 108 var pythonPostRequestTransform = map[string]string{ 109 "manifest.json": ` 110 { 111 "file_list": [ 112 "middleware.py" 113 ], 114 "custom_middleware": { 115 "driver": "python", 116 "post": [{ 117 "name": "MyPostHook" 118 }] 119 } 120 } 121 `, 122 "middleware.py": ` 123 from tyk.decorators import * 124 from gateway import TykGateway as tyk 125 import json 126 127 @Hook 128 def MyPostHook(request, session, spec): 129 130 131 if request.object.url == "/test2": 132 if request.object.method != "POST": 133 request.object.return_overrides.response_code = 500 134 request.object.return_overrides.response_error = "'invalid method type'" 135 return request, session 136 request.object.url = "tyk://test-api-2/newpath" 137 request.object.method = "GET" 138 139 return request , session 140 `, 141 } 142 143 var pythonBundleWithPreHook = map[string]string{ 144 "manifest.json": ` 145 { 146 "file_list": [ 147 "middleware.py" 148 ], 149 "custom_middleware": { 150 "driver": "python", 151 "pre": [{ 152 "name": "MyPreHook" 153 }] 154 } 155 } 156 `, 157 "middleware.py": ` 158 from tyk.decorators import * 159 from gateway import TykGateway as tyk 160 161 @Hook 162 def MyPreHook(request, session, metadata, spec): 163 content_type = request.get_header("Content-Type") 164 if "json" in content_type: 165 if len(request.object.raw_body) <= 0: 166 request.object.return_overrides.response_code = 400 167 request.object.return_overrides.response_error = "Raw body field is empty" 168 return request, session, metadata 169 if "{}" not in request.object.body: 170 request.object.return_overrides.response_code = 400 171 request.object.return_overrides.response_error = "Body field doesn't match" 172 return request, session, metadata 173 if "multipart" in content_type: 174 if len(request.object.body) != 0: 175 request.object.return_overrides.response_code = 400 176 request.object.return_overrides.response_error = "Body field isn't empty" 177 if len(request.object.raw_body) <= 0: 178 request.object.return_overrides.response_code = 400 179 request.object.return_overrides.response_error = "Raw body field is empty" 180 return request, session, metadata 181 182 `, 183 } 184 185 func TestMain(m *testing.M) { 186 os.Exit(gateway.InitTestMain(context.Background(), m)) 187 } 188 189 func TestPythonBundles(t *testing.T) { 190 ts := gateway.StartTest(gateway.TestConfig{ 191 CoprocessConfig: config.CoProcessConfig{ 192 EnableCoProcess: true, 193 PythonPathPrefix: pkgPath, 194 }}) 195 defer ts.Close() 196 197 authCheckBundle := gateway.RegisterBundle("python_with_auth_check", pythonBundleWithAuthCheck) 198 postHookBundle := gateway.RegisterBundle("python_with_post_hook", pythonBundleWithPostHook) 199 preHookBundle := gateway.RegisterBundle("python_with_pre_hook", pythonBundleWithPreHook) 200 postRequestTransformHookBundle := gateway.RegisterBundle("python_post_with_request_transform_hook", pythonPostRequestTransform) 201 202 t.Run("Single-file bundle with authentication hook", func(t *testing.T) { 203 gateway.BuildAndLoadAPI(func(spec *gateway.APISpec) { 204 spec.Proxy.ListenPath = "/test-api/" 205 spec.UseKeylessAccess = false 206 spec.EnableCoProcessAuth = true 207 spec.CustomMiddlewareBundle = authCheckBundle 208 spec.VersionData.NotVersioned = true 209 }) 210 211 time.Sleep(1 * time.Second) 212 213 validAuth := map[string]string{"Authorization": "valid_token"} 214 invalidAuth := map[string]string{"Authorization": "invalid_token"} 215 216 ts.Run(t, []test.TestCase{ 217 {Path: "/test-api/", Code: http.StatusOK, Headers: validAuth}, 218 {Path: "/test-api/", Code: http.StatusForbidden, Headers: invalidAuth}, 219 {Path: "/test-api/", Code: http.StatusForbidden, Headers: validAuth}, 220 }...) 221 }) 222 223 t.Run("Auth with policy", func(t *testing.T) { 224 specs := gateway.BuildAndLoadAPI(func(spec *gateway.APISpec) { 225 spec.Auth.AuthHeaderName = "Authorization" 226 spec.Proxy.ListenPath = "/test-api/" 227 spec.UseKeylessAccess = false 228 spec.EnableCoProcessAuth = true 229 spec.CustomMiddlewareBundle = authCheckBundle 230 spec.VersionData.NotVersioned = true 231 }) 232 233 time.Sleep(1 * time.Second) 234 235 pID := gateway.CreatePolicy(func(p *user.Policy) { 236 p.QuotaMax = 1 237 p.QuotaRenewalRate = 60 238 p.AccessRights = map[string]user.AccessDefinition{"test": { 239 APIID: specs[0].APIID, 240 Versions: []string{"Default"}, 241 }} 242 }) 243 244 policyAuth := map[string]string{"authorization": "47a0c79c427728b3df4af62b9228c8ae11", "policy": pID} 245 246 ts.Run(t, []test.TestCase{ 247 {Path: "/test-api/", Code: http.StatusOK, Headers: policyAuth}, 248 {Path: "/test-api/", Code: http.StatusForbidden, Headers: policyAuth}, 249 }...) 250 }) 251 252 t.Run("Single-file bundle with post hook", func(t *testing.T) { 253 254 keyID := gateway.CreateSession(func(s *user.SessionState) { 255 s.MetaData = map[string]interface{}{ 256 "testkey": map[string]interface{}{"nestedkey": "nestedvalue"}, 257 "stringkey": "testvalue", 258 } 259 s.Mutex = &sync.RWMutex{} 260 }) 261 262 gateway.BuildAndLoadAPI(func(spec *gateway.APISpec) { 263 spec.Proxy.ListenPath = "/test-api-2/" 264 spec.UseKeylessAccess = false 265 spec.EnableCoProcessAuth = false 266 spec.CustomMiddlewareBundle = postHookBundle 267 spec.VersionData.NotVersioned = true 268 }) 269 270 time.Sleep(1 * time.Second) 271 272 auth := map[string]string{"Authorization": keyID} 273 274 ts.Run(t, []test.TestCase{ 275 {Path: "/test-api-2/", Code: http.StatusOK, Headers: auth}, 276 }...) 277 }) 278 279 t.Run("Single-file bundle with pre hook and UTF-8/non-UTF-8 request data", func(t *testing.T) { 280 gateway.BuildAndLoadAPI(func(spec *gateway.APISpec) { 281 spec.Proxy.ListenPath = "/test-api-2/" 282 spec.UseKeylessAccess = true 283 spec.EnableCoProcessAuth = false 284 spec.CustomMiddlewareBundle = preHookBundle 285 spec.VersionData.NotVersioned = true 286 }) 287 288 time.Sleep(1 * time.Second) 289 290 fileData := gateway.GenerateTestBinaryData() 291 var buf bytes.Buffer 292 multipartWriter := multipart.NewWriter(&buf) 293 file, err := multipartWriter.CreateFormFile("file", "test.bin") 294 if err != nil { 295 t.Fatalf("Couldn't use multipart writer: %s", err.Error()) 296 } 297 _, err = fileData.WriteTo(file) 298 if err != nil { 299 t.Fatalf("Couldn't write to multipart file: %s", err.Error()) 300 } 301 field, err := multipartWriter.CreateFormField("testfield") 302 if err != nil { 303 t.Fatalf("Couldn't use multipart writer: %s", err.Error()) 304 } 305 _, err = field.Write([]byte("testvalue")) 306 if err != nil { 307 t.Fatalf("Couldn't write to form field: %s", err.Error()) 308 } 309 err = multipartWriter.Close() 310 if err != nil { 311 t.Fatalf("Couldn't close multipart writer: %s", err.Error()) 312 } 313 314 ts.Run(t, []test.TestCase{ 315 {Path: "/test-api-2/", Code: http.StatusOK, Data: &buf, Headers: map[string]string{"Content-Type": multipartWriter.FormDataContentType()}}, 316 {Path: "/test-api-2/", Code: http.StatusOK, Data: "{}", Headers: map[string]string{"Content-Type": "application/json"}}, 317 }...) 318 }) 319 320 t.Run("python post hook with url rewrite and method transform", func(t *testing.T) { 321 gateway.BuildAndLoadAPI(func(spec *gateway.APISpec) { 322 spec.Proxy.ListenPath = "/test-api-1/" 323 spec.UseKeylessAccess = true 324 spec.EnableCoProcessAuth = false 325 spec.CustomMiddlewareBundle = postRequestTransformHookBundle 326 327 v1 := spec.VersionData.Versions["v1"] 328 v1.UseExtendedPaths = true 329 v1.ExtendedPaths.URLRewrite = []apidef.URLRewriteMeta{{ 330 Path: "/get", 331 Method: http.MethodGet, 332 MatchPattern: "/get", 333 RewriteTo: "/test2", 334 }} 335 336 v1.ExtendedPaths.MethodTransforms = []apidef.MethodTransformMeta{{ 337 Path: "/get", 338 Method: http.MethodGet, 339 ToMethod: http.MethodPost, 340 }} 341 342 spec.VersionData.Versions["v1"] = v1 343 344 }, func(spec *gateway.APISpec) { 345 spec.Name = "test-api-2" 346 spec.Proxy.ListenPath = "/test-api-2/" 347 spec.UseKeylessAccess = true 348 spec.EnableCoProcessAuth = false 349 spec.UseKeylessAccess = true 350 }) 351 352 time.Sleep(1 * time.Second) 353 354 ts.Run(t, []test.TestCase{ 355 {Path: "/test-api-1/get", Code: http.StatusOK, BodyMatch: "newpath"}, 356 {Path: "/test-api-1/get", Code: http.StatusOK, BodyMatch: "GET"}, 357 {Path: "/test-api-1/post", Code: http.StatusOK, BodyNotMatch: "newpath"}, 358 }...) 359 }) 360 }