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  }