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 }