-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Start
v4
versioning by supporting Echo v4.9.x
and some older vers…
…ions. Next release will remove extractors from this repo and start using Echo `v4.10.0` exposed ones.
- Loading branch information
Showing
6 changed files
with
227 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
// SPDX-License-Identifier: MIT | ||
// SPDX-FileCopyrightText: © 2016 LabStack and Echo contributors | ||
|
||
package echojwt | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/labstack/echo/v4" | ||
"github.com/labstack/echo/v4/middleware" | ||
"net/textproto" | ||
"strings" | ||
) | ||
|
||
const ( | ||
// extractorLimit is arbitrary number to limit values extractor can return. this limits possible resource exhaustion | ||
// attack vector | ||
extractorLimit = 20 | ||
) | ||
|
||
var errHeaderExtractorValueMissing = errors.New("missing value in request header") | ||
var errHeaderExtractorValueInvalid = errors.New("invalid value in request header") | ||
var errQueryExtractorValueMissing = errors.New("missing value in the query string") | ||
var errParamExtractorValueMissing = errors.New("missing value in path params") | ||
var errCookieExtractorValueMissing = errors.New("missing value in cookies") | ||
var errFormExtractorValueMissing = errors.New("missing value in the form") | ||
|
||
// CreateExtractors creates ValuesExtractors from given lookups. | ||
// Lookups is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used | ||
// to extract key from the request. | ||
// Possible values: | ||
// - "header:<name>" or "header:<name>:<cut-prefix>" | ||
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header | ||
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we | ||
// want to cut is `<auth-scheme> ` note the space at the end. | ||
// In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `. | ||
// - "query:<name>" | ||
// - "param:<name>" | ||
// - "form:<name>" | ||
// - "cookie:<name>" | ||
// | ||
// Multiple sources example: | ||
// - "header:Authorization,header:X-Api-Key" | ||
func CreateExtractors(lookups string) ([]middleware.ValuesExtractor, error) { | ||
if lookups == "" { | ||
return nil, nil | ||
} | ||
sources := strings.Split(lookups, ",") | ||
var extractors = make([]middleware.ValuesExtractor, 0) | ||
for _, source := range sources { | ||
parts := strings.Split(source, ":") | ||
if len(parts) < 2 { | ||
return nil, fmt.Errorf("extractor source for lookup could not be split into needed parts: %v", source) | ||
} | ||
|
||
switch parts[0] { | ||
case "query": | ||
extractors = append(extractors, valuesFromQuery(parts[1])) | ||
case "param": | ||
extractors = append(extractors, valuesFromParam(parts[1])) | ||
case "cookie": | ||
extractors = append(extractors, valuesFromCookie(parts[1])) | ||
case "form": | ||
extractors = append(extractors, valuesFromForm(parts[1])) | ||
case "header": | ||
prefix := "" | ||
if len(parts) > 2 { | ||
prefix = parts[2] | ||
} | ||
extractors = append(extractors, valuesFromHeader(parts[1], prefix)) | ||
} | ||
} | ||
return extractors, nil | ||
} | ||
|
||
// valuesFromHeader returns a functions that extracts values from the request header. | ||
// valuePrefix is parameter to remove first part (prefix) of the extracted value. This is useful if header value has static | ||
// prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we want to remove is `<auth-scheme> ` | ||
// note the space at the end. In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove | ||
// is `Basic `. In case of JWT tokens `Authorization: Bearer <token>` prefix is `Bearer `. | ||
// If prefix is left empty the whole value is returned. | ||
func valuesFromHeader(header string, valuePrefix string) middleware.ValuesExtractor { | ||
prefixLen := len(valuePrefix) | ||
// standard library parses http.Request header keys in canonical form but we may provide something else so fix this | ||
header = textproto.CanonicalMIMEHeaderKey(header) | ||
return func(c echo.Context) ([]string, error) { | ||
values := c.Request().Header.Values(header) | ||
if len(values) == 0 { | ||
return nil, errHeaderExtractorValueMissing | ||
} | ||
|
||
result := make([]string, 0) | ||
for i, value := range values { | ||
if prefixLen == 0 { | ||
result = append(result, value) | ||
if i >= extractorLimit-1 { | ||
break | ||
} | ||
continue | ||
} | ||
if len(value) > prefixLen && strings.EqualFold(value[:prefixLen], valuePrefix) { | ||
result = append(result, value[prefixLen:]) | ||
if i >= extractorLimit-1 { | ||
break | ||
} | ||
} | ||
} | ||
|
||
if len(result) == 0 { | ||
if prefixLen > 0 { | ||
return nil, errHeaderExtractorValueInvalid | ||
} | ||
return nil, errHeaderExtractorValueMissing | ||
} | ||
return result, nil | ||
} | ||
} | ||
|
||
// valuesFromQuery returns a function that extracts values from the query string. | ||
func valuesFromQuery(param string) middleware.ValuesExtractor { | ||
return func(c echo.Context) ([]string, error) { | ||
result := c.QueryParams()[param] | ||
if len(result) == 0 { | ||
return nil, errQueryExtractorValueMissing | ||
} else if len(result) > extractorLimit-1 { | ||
result = result[:extractorLimit] | ||
} | ||
return result, nil | ||
} | ||
} | ||
|
||
// valuesFromParam returns a function that extracts values from the url param string. | ||
func valuesFromParam(param string) middleware.ValuesExtractor { | ||
return func(c echo.Context) ([]string, error) { | ||
result := make([]string, 0) | ||
paramVales := c.ParamValues() | ||
for i, p := range c.ParamNames() { | ||
if param == p { | ||
result = append(result, paramVales[i]) | ||
if i >= extractorLimit-1 { | ||
break | ||
} | ||
} | ||
} | ||
if len(result) == 0 { | ||
return nil, errParamExtractorValueMissing | ||
} | ||
return result, nil | ||
} | ||
} | ||
|
||
// valuesFromCookie returns a function that extracts values from the named cookie. | ||
func valuesFromCookie(name string) middleware.ValuesExtractor { | ||
return func(c echo.Context) ([]string, error) { | ||
cookies := c.Cookies() | ||
if len(cookies) == 0 { | ||
return nil, errCookieExtractorValueMissing | ||
} | ||
|
||
result := make([]string, 0) | ||
for i, cookie := range cookies { | ||
if name == cookie.Name { | ||
result = append(result, cookie.Value) | ||
if i >= extractorLimit-1 { | ||
break | ||
} | ||
} | ||
} | ||
if len(result) == 0 { | ||
return nil, errCookieExtractorValueMissing | ||
} | ||
return result, nil | ||
} | ||
} | ||
|
||
// valuesFromForm returns a function that extracts values from the form field. | ||
func valuesFromForm(name string) middleware.ValuesExtractor { | ||
return func(c echo.Context) ([]string, error) { | ||
if c.Request().Form == nil { | ||
_ = c.Request().ParseMultipartForm(32 << 20) // same what `c.Request().FormValue(name)` does | ||
} | ||
values := c.Request().Form[name] | ||
if len(values) == 0 { | ||
return nil, errFormExtractorValueMissing | ||
} | ||
if len(values) > extractorLimit-1 { | ||
values = values[:extractorLimit] | ||
} | ||
result := append([]string{}, values...) | ||
return result, nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters