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

     1  package gateway
     2  
     3  import (
     4  	"crypto/md5"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/mitchellh/mapstructure"
    13  	"github.com/sirupsen/logrus"
    14  	xmlpath "gopkg.in/xmlpath.v2"
    15  
    16  	"github.com/TykTechnologies/tyk/apidef"
    17  	"github.com/TykTechnologies/tyk/regexp"
    18  )
    19  
    20  // IdExtractor is the base interface for an ID extractor.
    21  type IdExtractor interface {
    22  	ExtractAndCheck(*http.Request) (string, ReturnOverrides)
    23  	GenerateSessionID(string, BaseMiddleware) string
    24  }
    25  
    26  // BaseExtractor is the base structure for an ID extractor, it implements the IdExtractor interface. Other extractors may override some of its methods.
    27  type BaseExtractor struct {
    28  	Config  *apidef.MiddlewareIdExtractor
    29  	BaseMid BaseMiddleware
    30  	Spec    *APISpec
    31  }
    32  
    33  // ExtractAndCheck is called from the CP middleware, if ID extractor is enabled for the current API.
    34  func (e *BaseExtractor) ExtractAndCheck(r *http.Request) (sessionID string, returnOverrides ReturnOverrides) {
    35  	log.WithFields(logrus.Fields{
    36  		"prefix": "idextractor",
    37  	}).Error("This extractor doesn't implement an extraction method, rejecting.")
    38  	return "", ReturnOverrides{ResponseCode: 403, ResponseError: "Key not authorised"}
    39  }
    40  
    41  // ExtractHeader is used when a HeaderSource is specified.
    42  func (e *BaseExtractor) ExtractHeader(r *http.Request) (headerValue string, err error) {
    43  	headerName := e.Config.ExtractorConfig["header_name"].(string)
    44  	headerValue = r.Header.Get(headerName)
    45  	if headerValue == "" {
    46  		err = errors.New("Bad header value.")
    47  	}
    48  	return headerValue, err
    49  }
    50  
    51  // ExtractForm is used when a FormSource is specified.
    52  func (e *BaseExtractor) ExtractForm(r *http.Request, paramName string) (formValue string, err error) {
    53  	parseForm(r)
    54  
    55  	if paramName == "" {
    56  		return "", errors.New("no form param name set")
    57  	}
    58  
    59  	values := r.Form[paramName]
    60  	if len(values) == 0 {
    61  		return "", errors.New("no form value")
    62  	}
    63  
    64  	return strings.Join(values, ""), nil
    65  }
    66  
    67  // ExtractBody is used when BodySource is specified.
    68  func (e *BaseExtractor) ExtractBody(r *http.Request) (string, error) {
    69  	body, err := ioutil.ReadAll(r.Body)
    70  	if err != nil {
    71  		return "", err
    72  	}
    73  	return string(body), err
    74  }
    75  
    76  // Error is a helper for logging the extractor errors. It always returns HTTP 400 (so we don't expose any details).
    77  func (e *BaseExtractor) Error(r *http.Request, err error, message string) (returnOverrides ReturnOverrides) {
    78  	logEntry := getLogEntryForRequest(e.BaseMid.Logger(), r, "", nil)
    79  	logEntry.Info("Extractor error: ", message, ", ", err)
    80  
    81  	return ReturnOverrides{
    82  		ResponseCode:  400,
    83  		ResponseError: "Authorization field missing",
    84  	}
    85  }
    86  
    87  // GenerateSessionID is a helper for generating session IDs, it takes an input (usually the extractor output) and a middleware pointer.
    88  func (e *BaseExtractor) GenerateSessionID(input string, mw BaseMiddleware) (sessionID string) {
    89  	data := []byte(input)
    90  	tokenID := fmt.Sprintf("%x", md5.Sum(data))
    91  	sessionID = generateToken(mw.Spec.OrgID, tokenID)
    92  	return sessionID
    93  }
    94  
    95  type ValueExtractor struct {
    96  	BaseExtractor
    97  	cfg *ValueExtractorConfig
    98  }
    99  
   100  type ValueExtractorConfig struct {
   101  	HeaderName    string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
   102  	FormParamName string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
   103  }
   104  
   105  func (e *ValueExtractor) Extract(input interface{}) string {
   106  	headerValue := input.(string)
   107  	return headerValue
   108  }
   109  
   110  func (e *ValueExtractor) ExtractAndCheck(r *http.Request) (sessionID string, returnOverrides ReturnOverrides) {
   111  	if e.cfg == nil {
   112  		config := &ValueExtractorConfig{}
   113  		if err := mapstructure.Decode(e.Config.ExtractorConfig, config); err != nil {
   114  			returnOverrides = e.Error(r, err, "Couldn't decode ValueExtractor configuration")
   115  			return sessionID, returnOverrides
   116  		}
   117  		e.cfg = config
   118  	}
   119  
   120  	var extractorOutput string
   121  	var err error
   122  	switch e.Config.ExtractFrom {
   123  	case apidef.HeaderSource:
   124  		extractorOutput, err = e.ExtractHeader(r)
   125  	case apidef.FormSource:
   126  		extractorOutput, err = e.ExtractForm(r, e.cfg.FormParamName)
   127  	}
   128  
   129  	if err != nil {
   130  		returnOverrides = e.Error(r, err, "ValueExtractor error")
   131  		return sessionID, returnOverrides
   132  	}
   133  
   134  	sessionID = e.GenerateSessionID(extractorOutput, e.BaseMid)
   135  
   136  	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(&sessionID, r)
   137  
   138  	if keyExists {
   139  		if previousSession.IdExtractorDeadline > time.Now().Unix() {
   140  			ctxSetSession(r, &previousSession, sessionID, true)
   141  			returnOverrides = ReturnOverrides{
   142  				ResponseCode: 200,
   143  			}
   144  		}
   145  	}
   146  
   147  	return sessionID, returnOverrides
   148  }
   149  
   150  type RegexExtractor struct {
   151  	BaseExtractor
   152  	compiledExpr *regexp.Regexp
   153  	cfg          *RegexExtractorConfig
   154  }
   155  
   156  type RegexExtractorConfig struct {
   157  	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
   158  	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
   159  	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
   160  	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
   161  }
   162  
   163  func (e *RegexExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
   164  	// Parse specific configuration settings:
   165  	if e.cfg == nil {
   166  		config := &RegexExtractorConfig{}
   167  		if err := mapstructure.Decode(e.Config.ExtractorConfig, config); err != nil {
   168  			returnOverrides = e.Error(r, err, "Can't decode RegexExtractor configuration")
   169  			return SessionID, returnOverrides
   170  		}
   171  		e.cfg = config
   172  	}
   173  
   174  	if e.Config.ExtractorConfig["regex_expression"] == nil {
   175  		returnOverrides = e.Error(r, nil, "RegexExtractor expects an expression")
   176  		return SessionID, returnOverrides
   177  	}
   178  
   179  	var err error
   180  	if e.compiledExpr == nil {
   181  		e.compiledExpr, err = regexp.Compile(e.cfg.RegexExpression)
   182  		if err != nil {
   183  			returnOverrides = e.Error(r, nil, "RegexExtractor found an invalid expression")
   184  			return SessionID, returnOverrides
   185  		}
   186  	}
   187  
   188  	var extractorOutput string
   189  	switch e.Config.ExtractFrom {
   190  	case apidef.HeaderSource:
   191  		extractorOutput, err = e.ExtractHeader(r)
   192  	case apidef.BodySource:
   193  		extractorOutput, err = e.ExtractBody(r)
   194  	case apidef.FormSource:
   195  		extractorOutput, err = e.ExtractForm(r, e.cfg.FormParamName)
   196  	}
   197  	if err != nil {
   198  		returnOverrides = e.Error(r, err, "RegexExtractor error")
   199  		return SessionID, returnOverrides
   200  	}
   201  
   202  	regexOutput := e.compiledExpr.FindAllString(extractorOutput, -1)
   203  	if e.cfg.RegexMatchIndex > len(regexOutput)-1 {
   204  		returnOverrides = e.Error(r, fmt.Errorf("Can't find regexp match group"), "RegexExtractor error")
   205  		return SessionID, returnOverrides
   206  	}
   207  
   208  	SessionID = e.GenerateSessionID(regexOutput[e.cfg.RegexMatchIndex], e.BaseMid)
   209  	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(&SessionID, r)
   210  
   211  	if keyExists {
   212  		if previousSession.IdExtractorDeadline > time.Now().Unix() {
   213  			ctxSetSession(r, &previousSession, SessionID, true)
   214  			returnOverrides = ReturnOverrides{
   215  				ResponseCode: 200,
   216  			}
   217  		}
   218  	}
   219  	return SessionID, returnOverrides
   220  }
   221  
   222  type XPathExtractor struct {
   223  	BaseExtractor
   224  	cfg  *XPathExtractorConfig
   225  	path *xmlpath.Path
   226  }
   227  
   228  type XPathExtractorConfig struct {
   229  	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
   230  	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
   231  	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
   232  	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
   233  }
   234  
   235  func (e *XPathExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
   236  	var err error
   237  	config := &XPathExtractorConfig{}
   238  	if err = mapstructure.Decode(e.Config.ExtractorConfig, config); err != nil {
   239  		returnOverrides = e.Error(r, err, "Can't decode XPathExtractor configuration")
   240  		return SessionID, returnOverrides
   241  	}
   242  	e.cfg = config
   243  	if e.Config.ExtractorConfig["xpath_expression"] == nil {
   244  		returnOverrides = e.Error(r, err, "XPathExtractor: no expression set")
   245  		return SessionID, returnOverrides
   246  	}
   247  
   248  	if e.path == nil {
   249  		expressionString := e.Config.ExtractorConfig["xpath_expression"].(string)
   250  		e.path, err = xmlpath.Compile(expressionString)
   251  		if err != nil {
   252  			returnOverrides = e.Error(r, err, "XPathExtractor: bad expression")
   253  			return SessionID, returnOverrides
   254  		}
   255  	}
   256  
   257  	var extractorOutput string
   258  	switch e.Config.ExtractFrom {
   259  	case apidef.HeaderSource:
   260  		extractorOutput, err = e.ExtractHeader(r)
   261  	case apidef.BodySource:
   262  		extractorOutput, err = e.ExtractBody(r)
   263  	case apidef.FormSource:
   264  		extractorOutput, err = e.ExtractForm(r, e.cfg.FormParamName)
   265  	}
   266  	if err != nil {
   267  		returnOverrides = e.Error(r, err, "XPathExtractor error")
   268  		return SessionID, returnOverrides
   269  	}
   270  
   271  	extractedXml, err := xmlpath.Parse(strings.NewReader(extractorOutput))
   272  	if err != nil {
   273  		returnOverrides = e.Error(r, err, "XPathExtractor: couldn't parse input")
   274  		return SessionID, returnOverrides
   275  	}
   276  
   277  	output, ok := e.path.String(extractedXml)
   278  	if !ok {
   279  		returnOverrides = e.Error(r, err, "XPathExtractor: no input")
   280  		return SessionID, returnOverrides
   281  	}
   282  
   283  	SessionID = e.GenerateSessionID(output, e.BaseMid)
   284  
   285  	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(&SessionID, r)
   286  	if keyExists {
   287  		if previousSession.IdExtractorDeadline > time.Now().Unix() {
   288  			ctxSetSession(r, &previousSession, SessionID, true)
   289  			returnOverrides = ReturnOverrides{
   290  				ResponseCode: 200,
   291  			}
   292  		}
   293  	}
   294  
   295  	return SessionID, returnOverrides
   296  }
   297  
   298  // newExtractor is called from the CP middleware for every API that specifies extractor settings.
   299  func newExtractor(referenceSpec *APISpec, mw BaseMiddleware) {
   300  	var extractor IdExtractor
   301  
   302  	baseExtractor := BaseExtractor{&referenceSpec.CustomMiddleware.IdExtractor, mw, referenceSpec}
   303  
   304  	// Initialize a extractor based on the API spec.
   305  	switch referenceSpec.CustomMiddleware.IdExtractor.ExtractWith {
   306  	case apidef.ValueExtractor:
   307  		extractor = &ValueExtractor{baseExtractor, nil}
   308  	case apidef.RegexExtractor:
   309  		extractor = &RegexExtractor{baseExtractor, nil, nil}
   310  	case apidef.XPathExtractor:
   311  		extractor = &XPathExtractor{baseExtractor, nil, nil}
   312  	}
   313  
   314  	referenceSpec.CustomMiddleware.IdExtractor.Extractor = extractor
   315  }