github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/mw_transform_jq.go (about)

     1  // +build jq
     2  
     3  package gateway
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/json"
     8  	"errors"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  
    13  	"github.com/sirupsen/logrus"
    14  
    15  	"github.com/TykTechnologies/tyk/apidef"
    16  )
    17  
    18  type TransformJQMiddleware struct {
    19  	BaseMiddleware
    20  }
    21  
    22  // JQResult structure stores the result of Tyk-JQ filter.
    23  // It means that the result of the filter must contains the following fields
    24  type JQResult struct {
    25  	Body           interface{}            `mapstructure:"body"`
    26  	RewriteHeaders map[string]string      `mapstructure:"rewrite_headers"`
    27  	TykContext     map[string]interface{} `mapstructure:"tyk_context"`
    28  }
    29  
    30  func (t *TransformJQMiddleware) Name() string {
    31  	return "TransformJQMiddleware"
    32  }
    33  
    34  func (t *TransformJQMiddleware) EnabledForSpec() bool {
    35  	for _, version := range t.Spec.VersionData.Versions {
    36  		if len(version.ExtendedPaths.TransformJQ) > 0 {
    37  			return true
    38  		}
    39  	}
    40  	return false
    41  }
    42  
    43  // ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail
    44  func (t *TransformJQMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {
    45  	_, versionPaths, _, _ := t.Spec.Version(r)
    46  	found, meta := t.Spec.CheckSpecMatchesStatus(r, versionPaths, TransformedJQ)
    47  	if !found {
    48  		return nil, http.StatusOK
    49  	}
    50  
    51  	err := t.transformJQBody(r, meta.(*TransformJQSpec))
    52  	if err != nil {
    53  		log.WithFields(logrus.Fields{
    54  			"prefix":      "inbound-transform-jq",
    55  			"server_name": t.Spec.Proxy.TargetURL,
    56  			"api_id":      t.Spec.APIID,
    57  			"path":        r.URL.Path,
    58  		}).Error(err)
    59  		return err, 415
    60  	}
    61  	return nil, http.StatusOK
    62  }
    63  
    64  func (t *TransformJQMiddleware) transformJQBody(r *http.Request, ts *TransformJQSpec) error {
    65  	defer r.Body.Close()
    66  
    67  	var bodyObj interface{}
    68  	dec := json.NewDecoder(r.Body)
    69  	err := dec.Decode(&bodyObj)
    70  
    71  	// Do not fail if the body is empty
    72  	if err != nil && err != io.EOF {
    73  		return err
    74  	}
    75  
    76  	jqObj := map[string]interface{}{
    77  		"body":         bodyObj,
    78  		"_tyk_context": ctxGetData(r),
    79  	}
    80  
    81  	jqResult, err := lockedJQTransform(t.Spec, ts, jqObj)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	transformed, _ := json.Marshal(jqResult.Body)
    87  	bodyBuffer := bytes.NewBuffer(transformed)
    88  	r.Body = ioutil.NopCloser(bodyBuffer)
    89  	r.ContentLength = int64(bodyBuffer.Len())
    90  
    91  	// Replace header in the request
    92  	for hName, hValue := range jqResult.RewriteHeaders {
    93  		r.Header.Set(hName, hValue)
    94  	}
    95  
    96  	if t.Spec.EnableContextVars {
    97  		// Set variables in context vars
    98  		contextDataObject := ctxGetData(r)
    99  		for k, v := range jqResult.TykContext {
   100  			contextDataObject[k] = v
   101  		}
   102  		ctxSetData(r, contextDataObject)
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  func lockedJQTransform(s *APISpec, t *TransformJQSpec, jqObj map[string]interface{}) (JQResult, error) {
   109  	s.Lock()
   110  	value, err := t.JQFilter.Handle(jqObj)
   111  	s.Unlock()
   112  	if err != nil {
   113  		return JQResult{}, err
   114  	}
   115  	// The filter MUST return the following JSON object
   116  	//  {
   117  	//    "body": THE_TRANSFORMED_BODY,
   118  	//    "rewrite_headers": {"header1_name": "header1_value", ...},
   119  	//    "tyk_context": {"var1_name": "var1_value", ...}
   120  	//  }
   121  
   122  	var jqResult JQResult
   123  	values, ok := value.(map[string]interface{})
   124  	if !ok {
   125  		return JQResult{}, errors.New("Invalid JSON object returned by JQ filter. Allowed field are 'body', 'rewrite_headers' and 'tyk_context'")
   126  	}
   127  
   128  	jqResult.Body = values["body"]
   129  
   130  	headers, converted := values["rewrite_headers"].(map[string]interface{})
   131  	if !converted {
   132  		log.Error("rewrite_headers field must be a JSON object of string/string pairs")
   133  	} else {
   134  		jqResult.RewriteHeaders = make(map[string]string)
   135  		for k, v := range headers {
   136  			switch x := v.(type) {
   137  			case string:
   138  				jqResult.RewriteHeaders[k] = x
   139  			default:
   140  				log.Errorf("rewrite_header field must be a JSON object of string/string pairs (%s isn't)", k)
   141  			}
   142  		}
   143  	}
   144  
   145  	jqResult.TykContext, _ = values["tyk_context"].(map[string]interface{})
   146  
   147  	return jqResult, nil
   148  }
   149  
   150  type TransformJQSpec struct {
   151  	apidef.TransformJQMeta
   152  	JQFilter *JQ
   153  }
   154  
   155  func (a *APIDefinitionLoader) compileTransformJQPathSpec(paths []apidef.TransformJQMeta, stat URLStatus) []URLSpec {
   156  	urlSpec := []URLSpec{}
   157  
   158  	log.Debug("Checking for JQ tranform paths ...")
   159  	for _, stringSpec := range paths {
   160  		newSpec := URLSpec{}
   161  		a.generateRegex(stringSpec.Path, &newSpec, stat)
   162  		newTransformSpec := TransformJQSpec{TransformJQMeta: stringSpec}
   163  
   164  		var err error
   165  		newTransformSpec.JQFilter, err = NewJQ(stringSpec.Filter)
   166  
   167  		if stat == TransformedJQ {
   168  			newSpec.TransformJQAction = newTransformSpec
   169  		} else {
   170  			newSpec.TransformJQResponseAction = newTransformSpec
   171  		}
   172  
   173  		if err == nil {
   174  			urlSpec = append(urlSpec, newSpec)
   175  		} else {
   176  			log.Error("JQ Filter load failure! Skipping transformation: ", err)
   177  		}
   178  	}
   179  
   180  	return urlSpec
   181  }