Remove Auth0 specific code (fixes #82) (#83)

This commit is contained in:
Mathieu Leplatre 2018-01-17 15:50:04 +01:00 коммит произвёл GitHub
Родитель e056e5bff6
Коммит b969c196d5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 493 добавлений и 217 удалений

58
Gopkg.lock сгенерированный
Просмотреть файл

@ -2,9 +2,13 @@
[[projects]]
name = "github.com/auth0-community/go-auth0"
packages = ["."]
revision = "3de339a55934c49fa256181e63b9ecf0234c15b7"
name = "github.com/allegro/bigcache"
packages = [
".",
"queue"
]
revision = "aa76879d59fa5d93c43680238227dbf7a53f5c28"
version = "v1.0.0"
[[projects]]
name = "github.com/davecgh/go-spew"
@ -19,15 +23,14 @@
[[projects]]
name = "github.com/gin-gonic/gin"
packages = [".","binding","render"]
packages = [
".",
"binding",
"render"
]
revision = "d459835d2b077e44f7c9b453505ee29881d5d12d"
version = "v1.2"
[[projects]]
name = "github.com/go-errors/errors"
packages = ["."]
revision = "8fa88b06e5974e97fbf9899a7f86a344bfd1f105"
[[projects]]
name = "github.com/golang/protobuf"
packages = ["proto"]
@ -36,7 +39,10 @@
[[projects]]
branch = "master"
name = "github.com/hashicorp/golang-lru"
packages = [".","simplelru"]
packages = [
".",
"simplelru"
]
revision = "0a025b7e63adc15a622f29b0b2c4c3848243bbf6"
[[projects]]
@ -46,7 +52,11 @@
[[projects]]
name = "github.com/ory/ladon"
packages = [".","compiler","manager/memory"]
packages = [
".",
"compiler",
"manager/memory"
]
revision = "8128aeca0a774620b4f3be9fb2a1f7c53eac662c"
version = "v0.8.5"
@ -80,7 +90,11 @@
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert","mock","require"]
packages = [
"assert",
"mock",
"require"
]
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
version = "v1.1.4"
@ -97,12 +111,19 @@
[[projects]]
name = "golang.org/x/crypto"
packages = ["ed25519","ed25519/internal/edwards25519","ssh/terminal"]
packages = [
"ed25519",
"ed25519/internal/edwards25519",
"ssh/terminal"
]
revision = "9419663f5a44be8b34ca85f08abc5fe1be11f8a3"
[[projects]]
name = "golang.org/x/sys"
packages = ["unix","windows"]
packages = [
"unix",
"windows"
]
revision = "6faef541c73732f438fb660a212750a9ba9f9362"
[[projects]]
@ -113,7 +134,12 @@
[[projects]]
name = "gopkg.in/square/go-jose.v2"
packages = [".","cipher","json","jwt"]
packages = [
".",
"cipher",
"json",
"jwt"
]
revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
version = "v2.1.3"
@ -125,6 +151,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "07a2200fcb41c7086e2701672735d5624f415b92ef410fbca16ef98a5169ec46"
inputs-digest = "701ae34e1b1d0cb8a416648b360e0c919e1e130768fc074bf1d3304f55dd8387"
solver-name = "gps-cdcl"
solver-version = 1

Просмотреть файл

@ -43,3 +43,11 @@
[[constraint]]
name = "gopkg.in/square/go-jose.v2"
version = "2.1.3"
[[constraint]]
name = "github.com/allegro/bigcache"
version = "1.0.0"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"

Просмотреть файл

@ -154,7 +154,7 @@ func TestBadServicesConfig(t *testing.T) {
// Bad JWT issuer
err = d.LoadPolicies(ServicesConfig{
ServiceConfig{
JWTIssuer: "https://perlin-pinpin",
JWTIssuer: "http://perlin-pinpin",
},
})
assert.NotNil(t, err)
@ -192,10 +192,10 @@ func TestLoadPoliciesTwice(t *testing.T) {
// Load bad policies, does not affect existing.
err := doorman.LoadPolicies(ServicesConfig{
ServiceConfig{
JWTIssuer: "https://perlin-pinpin",
JWTIssuer: "http://perlin-pinpin",
},
})
assert.Contains(t, err.Error(), "issuer \"https://perlin-pinpin\" not supported or has bad format")
assert.Contains(t, err.Error(), "issuer \"http://perlin-pinpin\" not supported or has bad format")
_, ok := doorman.ladons["https://sample.yaml"]
assert.True(t, ok)
}

Просмотреть файл

@ -71,7 +71,7 @@ func TestAllowedVerifiesJWT(t *testing.T) {
var response ErrorResponse
// Missing Authorization header.
performAllowed(t, r, body, http.StatusUnauthorized, &response)
assert.Equal(t, "Token not found", response.Message)
assert.Equal(t, "token not found", response.Message)
}
func TestAllowedHandlerBadRequest(t *testing.T) {

Просмотреть файл

@ -1,24 +1,14 @@
package doorman
import (
"fmt"
"net/http"
"strings"
jwt "gopkg.in/square/go-jose.v2/jwt"
)
// Claims is the set of information we extract from the JWT payload.
type Claims struct {
Subject string `json:"sub,omitempty"`
Audience jwt.Audience `json:"aud,omitempty"`
Email string `json:"email,omitempty"`
Groups []string `json:"groups,omitempty"`
}
// JWTValidator is the interface in charge of extracting JWT claims from request.
type JWTValidator interface {
Initialize() error
ExtractClaims(*http.Request) (*Claims, error)
ValidateRequest(*http.Request) (*Claims, error)
}
var jwtValidators map[string]JWTValidator
@ -29,24 +19,14 @@ func init() {
// NewJWTValidator instantiates a JWT validator for the specified issuer.
func NewJWTValidator(issuer string) (JWTValidator, error) {
if !strings.HasPrefix(issuer, "https://") {
return nil, fmt.Errorf("issuer %q not supported or has bad format", issuer)
}
// Reuse JWT validators instances among configs if they are for the same issuer.
v, ok := jwtValidators[issuer]
if !ok {
if strings.Contains(issuer, "mozilla.auth0.com") {
v = &MozillaAuth0Validator{
Issuer: issuer,
}
} else {
// Fallback on basic Auth0.
// XXX: Here is where we can add other Identity providers.
v = &Auth0Validator{
Issuer: issuer,
}
}
err := v.Initialize()
if err != nil {
return nil, err
}
v = newJWTGenericValidator(issuer)
jwtValidators[issuer] = v
}
return v, nil

Просмотреть файл

@ -1,51 +0,0 @@
package doorman
import (
"fmt"
"net/http"
"strings"
auth0 "github.com/auth0-community/go-auth0"
log "github.com/sirupsen/logrus"
jose "gopkg.in/square/go-jose.v2"
)
// Auth0Validator is the implementation of JWTValidator for Auth0.
type Auth0Validator struct {
Issuer string
validator *auth0.JWTValidator
}
// Initialize will fetch Auth0 public keys and instantiate a validator.
func (v *Auth0Validator) Initialize() error {
validator, err := auth0Validator(v.Issuer)
if err != nil {
return err
}
v.validator = validator
return nil
}
// ExtractClaims validates the token from request, and returns the JWT claims.
func (v *Auth0Validator) ExtractClaims(request *http.Request) (*Claims, error) {
token, err := v.validator.ValidateRequest(request)
claims := Claims{}
err = v.validator.Claims(request, token, &claims)
if err != nil {
return nil, err
}
return &claims, nil
}
func auth0Validator(issuer string) (*auth0.JWTValidator, error) {
if !strings.HasPrefix(issuer, "https://") || !strings.HasSuffix(issuer, "auth0.com/") {
return nil, fmt.Errorf("issuer %q not supported or has bad format", issuer)
}
jwksURI := fmt.Sprintf("%s.well-known/jwks.json", issuer)
log.Infof("JWT keys: %s", jwksURI)
// Will check audience only when request comes in, leave empty for now.
audience := []string{}
client := auth0.NewJWKClient(auth0.JWKClientOptions{URI: jwksURI})
config := auth0.NewConfiguration(client, audience, issuer, jose.RS256)
return auth0.NewValidator(config), nil
}

Просмотреть файл

@ -1,43 +0,0 @@
package doorman
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMozillaAuth0Initialize(t *testing.T) {
// No trailing slash
validator := MozillaAuth0Validator{"https://auth.mozilla.auth0.com", nil}
err := validator.Initialize()
assert.NotNil(t, err)
validator = MozillaAuth0Validator{"https://auth.mozilla.auth0.com/", nil}
err = validator.Initialize()
assert.Nil(t, err)
}
func TestMozillaAuth0ExtractClaims(t *testing.T) {
var err error
validator := MozillaAuth0Validator{"https://auth.mozilla.auth0.com/", nil}
err = validator.Initialize()
require.Nil(t, err)
r, _ := http.NewRequest("GET", "/", nil)
r.Header.Set("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1rWkRORGN5UmtOR1JURkROamxCTmpaRk9FSkJOMFpCTnpKQlFUTkVNRGhDTUVFd05rRkdPQSJ9.eyJuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwiZmFtaWx5X25hbWUiOiJMZXBsYXRyZSIsIm5pY2tuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci85NzE5N2YwMTFhM2Q5ZDQ5NGFlODEzNTY2ZjI0Njc5YT9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRm1sLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE3LTEyLTA0VDE1OjUyOjMzLjc2MVoiLCJpc3MiOiJodHRwczovL2F1dGgubW96aWxsYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8TW96aWxsYS1MREFQfG1sZXBsYXRyZSIsImF1ZCI6IlNMb2NmN1NhMWliZDVHTkpNTXFPNTM5ZzdjS3ZXQk9JIiwiZXhwIjoxNTEzMDA3NTcwLCJpYXQiOjE1MTI0MDI3NzAsImFtciI6WyJtZmEiXSwiYWNyIjoiaHR0cDovL3NjaGVtYXMub3BlbmlkLm5ldC9wYXBlL3BvbGljaWVzLzIwMDcvMDYvbXVsdGktZmFjdG9yIiwibm9uY2UiOiJQRkxyLmxtYWhCQWRYaEVSWm0zYVFxc2ZuWjhwcWt0VSIsImF0X2hhc2giOiJTN0Rha1BrZVA0Tnk4SWpTOGxnMHJBIiwiaHR0cHM6Ly9zc28ubW96aWxsYS5jb20vY2xhaW0vZ3JvdXBzIjpbIkludHJhbmV0V2lraSIsIlN0YXRzRGFzaGJvYXJkIiwicGhvbmVib29rX2FjY2VzcyIsImNvcnAtdnBuIiwidnBuX2NvcnAiLCJ2cG5fZGVmYXVsdCIsIkNsb3Vkc2VydmljZXNXaWtpIiwidGVhbV9tb2NvIiwiaXJjY2xvdWQiLCJva3RhX21mYSIsImNsb3Vkc2VydmljZXNfZGV2IiwidnBuX2tpbnRvMV9zdGFnZSIsInZwbl9raW50bzFfcHJvZCIsImVnZW5jaWFfZGUiLCJhY3RpdmVfc2NtX2xldmVsXzEiLCJhbGxfc2NtX2xldmVsXzEiLCJzZXJ2aWNlX3NhZmFyaWJvb2tzIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL2VtYWlscyI6WyJtbGVwbGF0cmVAbW96aWxsYS5jb20iLCJtYXRoaWV1QG1vemlsbGEuY29tIiwibWF0aGlldS5sZXBsYXRyZUBtb3ppbGxhLmNvbSJdLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9kbiI6Im1haWw9bWxlcGxhdHJlQG1vemlsbGEuY29tLG89Y29tLGRjPW1vemlsbGEiLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9vcmdhbml6YXRpb25Vbml0cyI6Im1haWw9bWxlcGxhdHJlQG1vemlsbGEuY29tLG89Y29tLGRjPW1vemlsbGEiLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9lbWFpbF9hbGlhc2VzIjpbIm1hdGhpZXVAbW96aWxsYS5jb20iLCJtYXRoaWV1LmxlcGxhdHJlQG1vemlsbGEuY29tIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL19IUkRhdGEiOnsicGxhY2Vob2xkZXIiOiJlbXB0eSJ9fQ.MK3Z1Nj15MfbM2TcO4FWVTTYPqAbUhL26pYOFa92mPnEUR2W_oJhwoZ8Vwq7dJcvTZfPq-aZKBnqHoPHHYlQbtaqfflhHmY9iRH0aPlxLQed_WVem4YqMn9xw0az4xHnf0UlzLU58kI97bqUFvvzs0fg_OTdDdO3owVUcaZrG8-xalCqQGQqwTfiH514gxeZ_Ki6610HSVDvpPvmODWPz87IDdgS6WkyM-SyAc3aYukP38aqRo-PUjEdpGbOtV_T_W2x8A3yQDxu0Bcq0WJz-FUEu2BHq1Vn6rmLm7BVYjDD6rYseusp8M0bvTfvXA-9OhJWGAAh6KrN9fnw7r30LQ")
claims, err := validator.ExtractClaims(r)
require.Nil(t, err)
assert.Contains(t, claims.Subject, "|Mozilla-LDAP|")
assert.Contains(t, claims.Email, "@mozilla.com")
assert.Contains(t, claims.Groups, "cloudservices_dev", "irccloud")
// Email provided in `email` field instead of https://sso.../emails list
r, _ = http.NewRequest("GET", "/", nil)
r.Header.Set("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1rWkRORGN5UmtOR1JURkROamxCTmpaRk9FSkJOMFpCTnpKQlFUTkVNRGhDTUVFd05rRkdPQSJ9.eyJuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwiZmFtaWx5X25hbWUiOiJMZXBsYXRyZSIsIm5pY2tuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci85NzE5N2YwMTFhM2Q5ZDQ5NGFlODEzNTY2ZjI0Njc5YT9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRm1sLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE3LTEyLTEzVDIzOjE0OjQ0LjUzOVoiLCJlbWFpbCI6Im1sZXBsYXRyZUBtb3ppbGxhLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL2F1dGgubW96aWxsYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8TW96aWxsYS1MREFQfG1sZXBsYXRyZSIsImF1ZCI6IlNMb2NmN1NhMWliZDVHTkpNTXFPNTM5ZzdjS3ZXQk9JIiwiZXhwIjoxNTEzODExNjg0LCJpYXQiOjE1MTMyMDY4ODQsIm5vbmNlIjoickhOSXF5bGM3SE54MmFhNjktay1SbVA1Y3VqVWNudUkiLCJhdF9oYXNoIjoiZllPZzB6elNHSk1ZWlZTNFRsLXV3dyIsImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL2dyb3VwcyI6WyJJbnRyYW5ldFdpa2kiLCJTdGF0c0Rhc2hib2FyZCIsInBob25lYm9va19hY2Nlc3MiLCJjb3JwLXZwbiIsInZwbl9jb3JwIiwidnBuX2RlZmF1bHQiLCJDbG91ZHNlcnZpY2VzV2lraSIsInRlYW1fbW9jbyIsImlyY2Nsb3VkIiwib2t0YV9tZmEiLCJjbG91ZHNlcnZpY2VzX2RldiIsInZwbl9raW50bzFfc3RhZ2UiLCJ2cG5fa2ludG8xX3Byb2QiLCJlZ2VuY2lhX2RlIiwiYWN0aXZlX3NjbV9sZXZlbF8xIiwiYWxsX3NjbV9sZXZlbF8xIiwic2VydmljZV9zYWZhcmlib29rcyIsImV2ZXJ5b25lIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL1JFQURNRV9GSVJTVCI6IlBsZWFzZSByZWZlciB0byBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS1pYW0vcGVyc29uLWFwaSBpbiBvcmRlciB0byBxdWVyeSBNb3ppbGxhIElBTSBDSVMgdXNlciBwcm9maWxlIGRhdGEifQ.EnF3oPHm90ZXnJ4egJqr-4eTaHMw-16beuZlvC66UsIehX7nBooP4VRfMW7KLwOHEnVVGV8jlxgn5p3Dnv1V_W6Yx4PLw7loeKrfhnEKw9onaH3frR_Vo0Y0-MgH4VnCbTwtGHsAfl32j2EoDljXYCqPhYCXD4H25o51lemAoKU3xWamF629FjooyhFTZPVI6JzKkOt39dQjALtXL9EVYRk0ameohHzOT0ZHA57H83FTrPmY_Jy5MWxv1aswcbzcENU1HsFEEkxkRCnGiosxYkStmDo957OQ0IXgNxdNe4VVXzuy5YiNmsjN-IF4tOADLFK5KnLHi4OBOGYiiRiJcQ")
claims, err = validator.ExtractClaims(r)
require.Nil(t, err)
assert.Contains(t, claims.Email, "@mozilla.com")
}

Просмотреть файл

@ -1,47 +0,0 @@
package doorman
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAuth0Initialize(t *testing.T) {
validator := Auth0Validator{"https://demo.oath-zero.com/", nil}
err := validator.Initialize()
assert.NotNil(t, err)
validator = Auth0Validator{"http://demo.oauth0.com/", nil}
err = validator.Initialize()
assert.NotNil(t, err)
}
func TestAuth0ExtractClaims(t *testing.T) {
var err error
validator := Auth0Validator{"https://minimal-demo-iam.auth0.com/", nil}
validator.Initialize()
r, _ := http.NewRequest("GET", "/", nil)
_, err = validator.ExtractClaims(r)
require.NotNil(t, err)
assert.Equal(t, "Token not found", err.Error())
r.Header.Set("Authorization", "Basic abc")
_, err = validator.ExtractClaims(r)
require.NotNil(t, err)
assert.Equal(t, "Token not found", err.Error())
r.Header.Set("Authorization", "Bearer abc zy")
_, err = validator.ExtractClaims(r)
require.NotNil(t, err)
assert.Equal(t, "square/go-jose: compact JWS format must have three parts", err.Error())
r.Header.Set("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5EZzFOemczTlRFeVEwVTFNMEZCTnpCQlFqa3hOVVk1UTBVMU9USXpOalEzUXpVek5UWkRNQSJ9.eyJpc3MiOiJodHRwczovL21pbmltYWwtZGVtby1pYW0uYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA0MTAyMzA2MTExMzUwNTc2NjI4IiwiYXVkIjpbImh0dHA6Ly9taW5pbWFsLWRlbW8taWFtLmxvY2FsaG9zdDo4MDAwIiwiaHR0cHM6Ly9taW5pbWFsLWRlbW8taWFtLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE1MDY2MDQzMTMsImV4cCI6MTUwNjYxMTUxMywiYXpwIjoiV1lSWXBKeVM1RG5EeXhMVFJWR0NRR0NXR28yS05RTE4iLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIn0.JmfQajLJ6UMU8sGwv-4FyN0hAPjlLnixoVXAJwn9-985Y4jnMNiG22RWAk5qsdhxVKjIsyQFGA2oHuKELfcrI-LEHX3dxePxx9jSGUdC1wzk3p2q3YCRwIV3DUFEtBVeml8gdB9V7tVBE6XDivfq7RphiC8c5zz28_vlB2iPPaAwfucJLc1d5t83xlBaSYU9-hWDet3HbgjQg4zvFat6C2-CuKkCuQEG92tsOdoD8RIJtlWmLiMVUhCFgr3pGa7_ZNiKmMFkgZiDsX2qqD107CfOLG3IutcLGCqlpHxOuVltGZNp3QCXwtjIoZSV-5IXssXKLYuz-75GpfEAmUB5fg")
claims, err := validator.ExtractClaims(r)
require.Nil(t, err)
assert.Equal(t, "google-oauth2|104102306111350576628", claims.Subject)
}

32
doorman/jwt_claims.go Normal file
Просмотреть файл

@ -0,0 +1,32 @@
package doorman
import (
jose "gopkg.in/square/go-jose.v2"
jwt "gopkg.in/square/go-jose.v2/jwt"
)
// Claims is the set of information we extract from the JWT payload.
type Claims struct {
Subject string `json:"sub,omitempty"`
Audience jwt.Audience `json:"aud,omitempty"`
Email string `json:"email,omitempty"`
Groups []string `json:"groups,omitempty"`
}
// ClaimExtractor is in charge of extracting meaningful info from JWT payload.
type ClaimExtractor interface {
Extract(*jwt.JSONWebToken, *jose.JSONWebKey) (*Claims, error)
}
type defaultClaimExtractor struct{}
func (*defaultClaimExtractor) Extract(token *jwt.JSONWebToken, key *jose.JSONWebKey) (*Claims, error) {
claims := &Claims{}
err := token.Claims(key, claims)
if err != nil {
return nil, err
}
return claims, nil
}
var defaultExtractor = &defaultClaimExtractor{}

Просмотреть файл

@ -1,9 +1,7 @@
package doorman
import (
"net/http"
auth0 "github.com/auth0-community/go-auth0"
jose "gopkg.in/square/go-jose.v2"
jwt "gopkg.in/square/go-jose.v2/jwt"
)
@ -16,27 +14,11 @@ type MozillaClaims struct {
Groups []string `json:"https://sso.mozilla.com/claim/groups"`
}
// MozillaAuth0Validator is the implementation of JWTValidator for Auth0.
type MozillaAuth0Validator struct {
Issuer string
validator *auth0.JWTValidator
}
type mozillaClaimExtractor struct{}
// Initialize will fetch Auth0 public keys and instantiate a validator.
func (v *MozillaAuth0Validator) Initialize() error {
validator, err := auth0Validator(v.Issuer)
if err != nil {
return err
}
v.validator = validator
return nil
}
// ExtractClaims validates the token from request, and returns the JWT claims.
func (v *MozillaAuth0Validator) ExtractClaims(request *http.Request) (*Claims, error) {
token, err := v.validator.ValidateRequest(request)
func (*mozillaClaimExtractor) Extract(token *jwt.JSONWebToken, key *jose.JSONWebKey) (*Claims, error) {
mozclaims := MozillaClaims{}
err = v.validator.Claims(request, token, &mozclaims)
err := token.Claims(key, &mozclaims)
if err != nil {
return nil, err
}
@ -53,5 +35,8 @@ func (v *MozillaAuth0Validator) ExtractClaims(request *http.Request) (*Claims, e
Email: email,
Groups: mozclaims.Groups,
}
return &claims, nil
}
var mozillaExtractor = &mozillaClaimExtractor{}

Просмотреть файл

@ -0,0 +1,32 @@
package doorman
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
jwt "gopkg.in/square/go-jose.v2/jwt"
)
func TestMozillaClaimsExtractor(t *testing.T) {
token, err := jwt.ParseSigned("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1rWkRORGN5UmtOR1JURkROamxCTmpaRk9FSkJOMFpCTnpKQlFUTkVNRGhDTUVFd05rRkdPQSJ9.eyJuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwiZmFtaWx5X25hbWUiOiJMZXBsYXRyZSIsIm5pY2tuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci85NzE5N2YwMTFhM2Q5ZDQ5NGFlODEzNTY2ZjI0Njc5YT9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRm1sLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE3LTEyLTA0VDE1OjUyOjMzLjc2MVoiLCJpc3MiOiJodHRwczovL2F1dGgubW96aWxsYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8TW96aWxsYS1MREFQfG1sZXBsYXRyZSIsImF1ZCI6IlNMb2NmN1NhMWliZDVHTkpNTXFPNTM5ZzdjS3ZXQk9JIiwiZXhwIjoxNTEzMDA3NTcwLCJpYXQiOjE1MTI0MDI3NzAsImFtciI6WyJtZmEiXSwiYWNyIjoiaHR0cDovL3NjaGVtYXMub3BlbmlkLm5ldC9wYXBlL3BvbGljaWVzLzIwMDcvMDYvbXVsdGktZmFjdG9yIiwibm9uY2UiOiJQRkxyLmxtYWhCQWRYaEVSWm0zYVFxc2ZuWjhwcWt0VSIsImF0X2hhc2giOiJTN0Rha1BrZVA0Tnk4SWpTOGxnMHJBIiwiaHR0cHM6Ly9zc28ubW96aWxsYS5jb20vY2xhaW0vZ3JvdXBzIjpbIkludHJhbmV0V2lraSIsIlN0YXRzRGFzaGJvYXJkIiwicGhvbmVib29rX2FjY2VzcyIsImNvcnAtdnBuIiwidnBuX2NvcnAiLCJ2cG5fZGVmYXVsdCIsIkNsb3Vkc2VydmljZXNXaWtpIiwidGVhbV9tb2NvIiwiaXJjY2xvdWQiLCJva3RhX21mYSIsImNsb3Vkc2VydmljZXNfZGV2IiwidnBuX2tpbnRvMV9zdGFnZSIsInZwbl9raW50bzFfcHJvZCIsImVnZW5jaWFfZGUiLCJhY3RpdmVfc2NtX2xldmVsXzEiLCJhbGxfc2NtX2xldmVsXzEiLCJzZXJ2aWNlX3NhZmFyaWJvb2tzIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL2VtYWlscyI6WyJtbGVwbGF0cmVAbW96aWxsYS5jb20iLCJtYXRoaWV1QG1vemlsbGEuY29tIiwibWF0aGlldS5sZXBsYXRyZUBtb3ppbGxhLmNvbSJdLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9kbiI6Im1haWw9bWxlcGxhdHJlQG1vemlsbGEuY29tLG89Y29tLGRjPW1vemlsbGEiLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9vcmdhbml6YXRpb25Vbml0cyI6Im1haWw9bWxlcGxhdHJlQG1vemlsbGEuY29tLG89Y29tLGRjPW1vemlsbGEiLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9lbWFpbF9hbGlhc2VzIjpbIm1hdGhpZXVAbW96aWxsYS5jb20iLCJtYXRoaWV1LmxlcGxhdHJlQG1vemlsbGEuY29tIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL19IUkRhdGEiOnsicGxhY2Vob2xkZXIiOiJlbXB0eSJ9fQ.MK3Z1Nj15MfbM2TcO4FWVTTYPqAbUhL26pYOFa92mPnEUR2W_oJhwoZ8Vwq7dJcvTZfPq-aZKBnqHoPHHYlQbtaqfflhHmY9iRH0aPlxLQed_WVem4YqMn9xw0az4xHnf0UlzLU58kI97bqUFvvzs0fg_OTdDdO3owVUcaZrG8-xalCqQGQqwTfiH514gxeZ_Ki6610HSVDvpPvmODWPz87IDdgS6WkyM-SyAc3aYukP38aqRo-PUjEdpGbOtV_T_W2x8A3yQDxu0Bcq0WJz-FUEu2BHq1Vn6rmLm7BVYjDD6rYseusp8M0bvTfvXA-9OhJWGAAh6KrN9fnw7r30LQ")
require.Nil(t, err)
validator := newJWTGenericValidator("https://auth.mozilla.auth0.com")
jwks, err := validator.jwks()
require.Nil(t, err)
key := &jwks.Keys[0]
claims, err := mozillaExtractor.Extract(token, key)
require.Nil(t, err)
assert.Contains(t, claims.Subject, "|Mozilla-LDAP|")
assert.Contains(t, claims.Email, "@mozilla.com")
assert.Contains(t, claims.Groups, "cloudservices_dev", "irccloud")
// Email provided in `email` field instead of https://sso.../emails list
token, err = jwt.ParseSigned("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1rWkRORGN5UmtOR1JURkROamxCTmpaRk9FSkJOMFpCTnpKQlFUTkVNRGhDTUVFd05rRkdPQSJ9.eyJuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwiZmFtaWx5X25hbWUiOiJMZXBsYXRyZSIsIm5pY2tuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci85NzE5N2YwMTFhM2Q5ZDQ5NGFlODEzNTY2ZjI0Njc5YT9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRm1sLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE3LTEyLTEzVDIzOjE0OjQ0LjUzOVoiLCJlbWFpbCI6Im1sZXBsYXRyZUBtb3ppbGxhLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL2F1dGgubW96aWxsYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8TW96aWxsYS1MREFQfG1sZXBsYXRyZSIsImF1ZCI6IlNMb2NmN1NhMWliZDVHTkpNTXFPNTM5ZzdjS3ZXQk9JIiwiZXhwIjoxNTEzODExNjg0LCJpYXQiOjE1MTMyMDY4ODQsIm5vbmNlIjoickhOSXF5bGM3SE54MmFhNjktay1SbVA1Y3VqVWNudUkiLCJhdF9oYXNoIjoiZllPZzB6elNHSk1ZWlZTNFRsLXV3dyIsImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL2dyb3VwcyI6WyJJbnRyYW5ldFdpa2kiLCJTdGF0c0Rhc2hib2FyZCIsInBob25lYm9va19hY2Nlc3MiLCJjb3JwLXZwbiIsInZwbl9jb3JwIiwidnBuX2RlZmF1bHQiLCJDbG91ZHNlcnZpY2VzV2lraSIsInRlYW1fbW9jbyIsImlyY2Nsb3VkIiwib2t0YV9tZmEiLCJjbG91ZHNlcnZpY2VzX2RldiIsInZwbl9raW50bzFfc3RhZ2UiLCJ2cG5fa2ludG8xX3Byb2QiLCJlZ2VuY2lhX2RlIiwiYWN0aXZlX3NjbV9sZXZlbF8xIiwiYWxsX3NjbV9sZXZlbF8xIiwic2VydmljZV9zYWZhcmlib29rcyIsImV2ZXJ5b25lIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL1JFQURNRV9GSVJTVCI6IlBsZWFzZSByZWZlciB0byBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS1pYW0vcGVyc29uLWFwaSBpbiBvcmRlciB0byBxdWVyeSBNb3ppbGxhIElBTSBDSVMgdXNlciBwcm9maWxlIGRhdGEifQ.EnF3oPHm90ZXnJ4egJqr-4eTaHMw-16beuZlvC66UsIehX7nBooP4VRfMW7KLwOHEnVVGV8jlxgn5p3Dnv1V_W6Yx4PLw7loeKrfhnEKw9onaH3frR_Vo0Y0-MgH4VnCbTwtGHsAfl32j2EoDljXYCqPhYCXD4H25o51lemAoKU3xWamF629FjooyhFTZPVI6JzKkOt39dQjALtXL9EVYRk0ameohHzOT0ZHA57H83FTrPmY_Jy5MWxv1aswcbzcENU1HsFEEkxkRCnGiosxYkStmDo957OQ0IXgNxdNe4VVXzuy5YiNmsjN-IF4tOADLFK5KnLHi4OBOGYiiRiJcQ")
require.Nil(t, err)
claims, err = mozillaExtractor.Extract(token, key)
require.Nil(t, err)
assert.Contains(t, claims.Email, "@mozilla.com")
}

Просмотреть файл

@ -0,0 +1,23 @@
package doorman
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
jwt "gopkg.in/square/go-jose.v2/jwt"
)
func TestDefaultClaimsExtractor(t *testing.T) {
token, err := jwt.ParseSigned("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5EZzFOemczTlRFeVEwVTFNMEZCTnpCQlFqa3hOVVk1UTBVMU9USXpOalEzUXpVek5UWkRNQSJ9.eyJpc3MiOiJodHRwczovL21pbmltYWwtZGVtby1pYW0uYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA0MTAyMzA2MTExMzUwNTc2NjI4IiwiYXVkIjpbImh0dHA6Ly9taW5pbWFsLWRlbW8taWFtLmxvY2FsaG9zdDo4MDAwIiwiaHR0cHM6Ly9taW5pbWFsLWRlbW8taWFtLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE1MDY2MDQzMTMsImV4cCI6MTUwNjYxMTUxMywiYXpwIjoiV1lSWXBKeVM1RG5EeXhMVFJWR0NRR0NXR28yS05RTE4iLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIn0.JmfQajLJ6UMU8sGwv-4FyN0hAPjlLnixoVXAJwn9-985Y4jnMNiG22RWAk5qsdhxVKjIsyQFGA2oHuKELfcrI-LEHX3dxePxx9jSGUdC1wzk3p2q3YCRwIV3DUFEtBVeml8gdB9V7tVBE6XDivfq7RphiC8c5zz28_vlB2iPPaAwfucJLc1d5t83xlBaSYU9-hWDet3HbgjQg4zvFat6C2-CuKkCuQEG92tsOdoD8RIJtlWmLiMVUhCFgr3pGa7_ZNiKmMFkgZiDsX2qqD107CfOLG3IutcLGCqlpHxOuVltGZNp3QCXwtjIoZSV-5IXssXKLYuz-75GpfEAmUB5fg")
require.Nil(t, err)
validator := newJWTGenericValidator("https://minimal-demo-iam.auth0.com")
jwks, err := validator.jwks()
require.Nil(t, err)
key := &jwks.Keys[0]
claims, err := defaultExtractor.Extract(token, key)
require.Nil(t, err)
assert.Equal(t, "google-oauth2|104102306111350576628", claims.Subject)
}

190
doorman/jwt_generic.go Normal file
Просмотреть файл

@ -0,0 +1,190 @@
package doorman
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/pkg/errors"
"github.com/allegro/bigcache"
jose "gopkg.in/square/go-jose.v2"
jwt "gopkg.in/square/go-jose.v2/jwt"
log "github.com/sirupsen/logrus"
)
// OpenIDConfiguration is the OpenID provider metadata about endpoints etc.
type OpenIDConfiguration struct {
JWKSUri string `json:"jwks_uri"`
}
// JWKS are the JWT public keys
type JWKS struct {
Keys []jose.JSONWebKey `json:"keys"`
}
type jwtGenericValidator struct {
Issuer string
ClaimExtractor ClaimExtractor
SignatureAlgorithm jose.SignatureAlgorithm
cache *bigcache.BigCache
}
// newJWTGenericValidator returns a generic JWT validator of this issuer.
func newJWTGenericValidator(issuer string) *jwtGenericValidator {
cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(1 * time.Hour))
var extractor ClaimExtractor = defaultExtractor
if strings.Contains(issuer, "mozilla.auth0.com") {
extractor = mozillaExtractor
}
return &jwtGenericValidator{
Issuer: issuer,
ClaimExtractor: extractor,
SignatureAlgorithm: jose.RS256,
cache: cache,
}
}
func (v *jwtGenericValidator) config() (*OpenIDConfiguration, error) {
cacheKey := "config:" + v.Issuer
data, err := v.cache.Get(cacheKey)
// Cache is empty or expired: fetch again.
if err != nil {
uri := strings.TrimRight(v.Issuer, "/") + "/.well-known/openid-configuration"
log.Debugf("Fetch OpenID configuration from %s", uri)
data, err = downloadJSON(uri)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch OpenID configuration")
}
v.cache.Set(cacheKey, data)
}
// XXX: since cache stores bytes, we parse it again at every usage :( ?
config := &OpenIDConfiguration{}
err = json.Unmarshal(data, config)
if err != nil {
return nil, errors.Wrap(err, "failed to parse OpenID configuration")
}
if config.JWKSUri == "" {
return nil, fmt.Errorf("no jwks_uri attribute in OpenID configuration")
}
return config, nil
}
func (v *jwtGenericValidator) jwks() (*JWKS, error) {
cacheKey := "jwks:" + v.Issuer
data, err := v.cache.Get(cacheKey)
// Cache is empty or expired: fetch again.
if err != nil {
config, err := v.config()
if err != nil {
return nil, err
}
uri := config.JWKSUri
log.Debugf("Fetch public keys from %s", uri)
data, err = downloadJSON(uri)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch JWKS")
}
v.cache.Set(cacheKey, data)
}
// XXX: since cache stores bytes, we parse it again at every usage :( ?
var jwks = &JWKS{}
err = json.Unmarshal(data, jwks)
if err != nil {
return nil, errors.Wrap(err, "failed to parse JWKS")
}
if len(jwks.Keys) < 1 {
return nil, fmt.Errorf("no JWKS found")
}
return jwks, nil
}
func (v *jwtGenericValidator) ValidateRequest(r *http.Request) (*Claims, error) {
// 1. Extract JWT from request headers
token, err := fromHeader(r)
if err != nil {
return nil, err
}
// 2. Read JWT headers
if len(token.Headers) < 1 {
return nil, fmt.Errorf("no headers in the token")
}
header := token.Headers[0]
if header.Algorithm != string(v.SignatureAlgorithm) {
return nil, fmt.Errorf("invalid algorithm")
}
// 3. Get public key with specified ID
keys, err := v.jwks()
if err != nil {
return nil, err
}
var key *jose.JSONWebKey
for _, k := range keys.Keys {
if k.KeyID == header.KeyID {
key = &k
break
}
}
if key == nil {
return nil, fmt.Errorf("no JWT key with id %q", header.KeyID)
}
// 4. Parse and verify signature.
jwtClaims := jwt.Claims{}
err = token.Claims(key, &jwtClaims)
if err != nil {
return nil, errors.Wrap(err, "failed to read JWT payload")
}
// 5. Validate issuer, claims and expiration.
// Will check audience only when request comes in, leave empty for now.
audience := []string{}
expected := jwt.Expected{Issuer: v.Issuer, Audience: audience}
expected = expected.WithTime(time.Now())
err = jwtClaims.Validate(expected)
if err != nil {
return nil, errors.Wrap(err, "invalid JWT claims")
}
// 6. Extract relevant claims for Doorman.
claims, err := v.ClaimExtractor.Extract(token, key)
if err != nil {
return nil, errors.Wrap(err, "failed to extract JWT claims")
}
return claims, nil
}
// fromHeader reads the authorization header value and parses it as JSON Web Token.
func fromHeader(r *http.Request) (*jwt.JSONWebToken, error) {
if authorizationHeader := r.Header.Get("Authorization"); len(authorizationHeader) > 7 && strings.EqualFold(authorizationHeader[0:7], "BEARER ") {
raw := []byte(authorizationHeader[7:])
return jwt.ParseSigned(string(raw))
}
return nil, fmt.Errorf("token not found")
}
func downloadJSON(uri string) ([]byte, error) {
response, err := http.Get(uri)
if err != nil {
return nil, err
}
if contentHeader := response.Header.Get("Content-Type"); !strings.HasPrefix(contentHeader, "application/json") {
return nil, fmt.Errorf("%s has not a JSON content-type", uri)
}
defer response.Body.Close()
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, errors.Wrap(err, "could not download JSON")
}
return data, nil
}

141
doorman/jwt_generic_test.go Normal file
Просмотреть файл

@ -0,0 +1,141 @@
package doorman
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFetchOpenIDConfiguration(t *testing.T) {
// Not available
validator := newJWTGenericValidator("https://missing.com")
_, err := validator.config()
require.NotNil(t, err)
assert.Contains(t, err.Error(), "connection refused")
// Bad content-type
validator = newJWTGenericValidator("https://mozilla.org")
_, err = validator.config()
require.NotNil(t, err)
assert.Contains(t, err.Error(), "has not a JSON content-type")
// Bad JSON
validator = newJWTGenericValidator("https://mozilla.org")
validator.cache.Set("config:https://mozilla.org", []byte("<html>"))
_, err = validator.config()
require.NotNil(t, err)
assert.Contains(t, err.Error(), "invalid character '<'")
// Good one
validator = newJWTGenericValidator("https://auth.mozilla.auth0.com/")
config, err := validator.config()
require.Nil(t, err)
assert.Contains(t, config.JWKSUri, ".well-known/jwks.json")
}
func TestDownloadKeys(t *testing.T) {
validator := newJWTGenericValidator("https://fake.com")
validator.cache.Set("config:https://fake.com",
[]byte("{\"jwks_uri\":\"http://z\"}"))
// Bad URL
_, err := validator.jwks()
require.NotNil(t, err)
assert.Contains(t, err.Error(), "no such host")
// Bad content-type
validator.cache.Set("config:https://fake.com",
[]byte("{\"jwks_uri\":\"http://mozilla.org\"}"))
_, err = validator.jwks()
require.NotNil(t, err)
assert.Contains(t, err.Error(), "has not a JSON content-type")
// Bad JSON
validator.cache.Set("jwks:https://fake.com", []byte("<html>"))
_, err = validator.jwks()
require.NotNil(t, err)
assert.Contains(t, err.Error(), "invalid character '<'")
// Missing Keys attribute
validator.cache.Set("jwks:https://fake.com", []byte("{}"))
_, err = validator.jwks()
require.NotNil(t, err)
assert.Contains(t, err.Error(), "no JWKS found")
// Good one
validator = newJWTGenericValidator("https://auth.mozilla.auth0.com")
keys, err := validator.jwks()
require.Nil(t, err)
assert.Equal(t, 1, len(keys.Keys))
}
func TestFromHeader(t *testing.T) {
r, _ := http.NewRequest("GET", "/", nil)
_, err := fromHeader(r)
require.NotNil(t, err)
assert.Equal(t, "token not found", err.Error())
r.Header.Set("Authorization", "Basic abc")
_, err = fromHeader(r)
require.NotNil(t, err)
assert.Equal(t, "token not found", err.Error())
r.Header.Set("Authorization", "Bearer abc zy")
_, err = fromHeader(r)
require.NotNil(t, err)
assert.Equal(t, "square/go-jose: compact JWS format must have three parts", err.Error())
}
func TestValidateRequest(t *testing.T) {
goodJWT := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1rWkRORGN5UmtOR1JURkROamxCTmpaRk9FSkJOMFpCTnpKQlFUTkVNRGhDTUVFd05rRkdPQSJ9.eyJuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwiZmFtaWx5X25hbWUiOiJMZXBsYXRyZSIsIm5pY2tuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci85NzE5N2YwMTFhM2Q5ZDQ5NGFlODEzNTY2ZjI0Njc5YT9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRm1sLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE3LTEyLTA0VDE1OjUyOjMzLjc2MVoiLCJpc3MiOiJodHRwczovL2F1dGgubW96aWxsYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8TW96aWxsYS1MREFQfG1sZXBsYXRyZSIsImF1ZCI6IlNMb2NmN1NhMWliZDVHTkpNTXFPNTM5ZzdjS3ZXQk9JIiwiZXhwIjoxNTEzMDA3NTcwLCJpYXQiOjE1MTI0MDI3NzAsImFtciI6WyJtZmEiXSwiYWNyIjoiaHR0cDovL3NjaGVtYXMub3BlbmlkLm5ldC9wYXBlL3BvbGljaWVzLzIwMDcvMDYvbXVsdGktZmFjdG9yIiwibm9uY2UiOiJQRkxyLmxtYWhCQWRYaEVSWm0zYVFxc2ZuWjhwcWt0VSIsImF0X2hhc2giOiJTN0Rha1BrZVA0Tnk4SWpTOGxnMHJBIiwiaHR0cHM6Ly9zc28ubW96aWxsYS5jb20vY2xhaW0vZ3JvdXBzIjpbIkludHJhbmV0V2lraSIsIlN0YXRzRGFzaGJvYXJkIiwicGhvbmVib29rX2FjY2VzcyIsImNvcnAtdnBuIiwidnBuX2NvcnAiLCJ2cG5fZGVmYXVsdCIsIkNsb3Vkc2VydmljZXNXaWtpIiwidGVhbV9tb2NvIiwiaXJjY2xvdWQiLCJva3RhX21mYSIsImNsb3Vkc2VydmljZXNfZGV2IiwidnBuX2tpbnRvMV9zdGFnZSIsInZwbl9raW50bzFfcHJvZCIsImVnZW5jaWFfZGUiLCJhY3RpdmVfc2NtX2xldmVsXzEiLCJhbGxfc2NtX2xldmVsXzEiLCJzZXJ2aWNlX3NhZmFyaWJvb2tzIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL2VtYWlscyI6WyJtbGVwbGF0cmVAbW96aWxsYS5jb20iLCJtYXRoaWV1QG1vemlsbGEuY29tIiwibWF0aGlldS5sZXBsYXRyZUBtb3ppbGxhLmNvbSJdLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9kbiI6Im1haWw9bWxlcGxhdHJlQG1vemlsbGEuY29tLG89Y29tLGRjPW1vemlsbGEiLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9vcmdhbml6YXRpb25Vbml0cyI6Im1haWw9bWxlcGxhdHJlQG1vemlsbGEuY29tLG89Y29tLGRjPW1vemlsbGEiLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9lbWFpbF9hbGlhc2VzIjpbIm1hdGhpZXVAbW96aWxsYS5jb20iLCJtYXRoaWV1LmxlcGxhdHJlQG1vemlsbGEuY29tIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL19IUkRhdGEiOnsicGxhY2Vob2xkZXIiOiJlbXB0eSJ9fQ.MK3Z1Nj15MfbM2TcO4FWVTTYPqAbUhL26pYOFa92mPnEUR2W_oJhwoZ8Vwq7dJcvTZfPq-aZKBnqHoPHHYlQbtaqfflhHmY9iRH0aPlxLQed_WVem4YqMn9xw0az4xHnf0UlzLU58kI97bqUFvvzs0fg_OTdDdO3owVUcaZrG8-xalCqQGQqwTfiH514gxeZ_Ki6610HSVDvpPvmODWPz87IDdgS6WkyM-SyAc3aYukP38aqRo-PUjEdpGbOtV_T_W2x8A3yQDxu0Bcq0WJz-FUEu2BHq1Vn6rmLm7BVYjDD6rYseusp8M0bvTfvXA-9OhJWGAAh6KrN9fnw7r30LQ"
r, _ := http.NewRequest("GET", "/", nil)
r.Header.Set("Authorization", "Bearer "+goodJWT)
// Fail to fetch JWKS
validator := newJWTGenericValidator("https://perlinpimpin.com")
_, err := validator.ValidateRequest(r)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "no such host")
validator = newJWTGenericValidator("https://auth.mozilla.auth0.com/")
// Cannot extract JWT
r.Header.Set("Authorization", "Bearer abc")
_, err = validator.ValidateRequest(r)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "compact JWS format must have three parts")
// Unknown public key
r.Header.Set("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFiYyJ9.abc.123")
_, err = validator.ValidateRequest(r)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "no JWT key with id \"abc\"")
// // Invalid algorithm
r.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ")
_, err = validator.ValidateRequest(r)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "invalid algorithm")
// Bad signature
r.Header.Set("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1rWkRORGN5UmtOR1JURkROamxCTmpaRk9FSkJOMFpCTnpKQlFUTkVNRGhDTUVFd05rRkdPQSJ9.eyJuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwiZmFtaWx5X25hbWUiOiJMZXBsYXRyZSIsIm5pY2tuYW1lIjoiTWF0aGlldSBMZXBsYXRyZSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci85NzE5N2YwMTFhM2Q5ZDQ5NGFlODEzNTY2ZjI0Njc5YT9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRm1sLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE3LTEyLTA0VDE1OjUyOjMzLjc2MVoiLCJpc3MiOiJodHRwczovL2F1dGgubW96aWxsYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8TW96aWxsYS1MREFQfG1sZXBsYXRyZSIsImF1ZCI6IlNMb2NmN1NhMWliZDVHTkpNTXFPNTM5ZzdjS3ZXQk9JIiwiZXhwIjoxNTEzMDA3NTcwLCJpYXQiOjE1MTI0MDI3NzAsImFtciI6WyJtZmEiXSwiYWNyIjoiaHR0cDovL3NjaGVtYXMub3BlbmlkLm5ldC9wYXBlL3BvbGljaWVzLzIwMDcvMDYvbXVsdGktZmFjdG9yIiwibm9uY2UiOiJQRkxyLmxtYWhCQWRYaEVSWm0zYVFxc2ZuWjhwcWt0VSIsImF0X2hhc2giOiJTN0Rha1BrZVA0Tnk4SWpTOGxnMHJBIiwiaHR0cHM6Ly9zc28ubW96aWxsYS5jb20vY2xhaW0vZ3JvdXBzIjpbIkludHJhbmV0V2lraSIsIlN0YXRzRGFzaGJvYXJkIiwicGhvbmVib29rX2FjY2VzcyIsImNvcnAtdnBuIiwidnBuX2NvcnAiLCJ2cG5fZGVmYXVsdCIsIkNsb3Vkc2VydmljZXNXaWtpIiwidGVhbV9tb2NvIiwiaXJjY2xvdWQiLCJva3RhX21mYSIsImNsb3Vkc2VydmljZXNfZGV2IiwidnBuX2tpbnRvMV9zdGFnZSIsInZwbl9raW50bzFfcHJvZCIsImVnZW5jaWFfZGUiLCJhY3RpdmVfc2NtX2xldmVsXzEiLCJhbGxfc2NtX2xldmVsXzEiLCJzZXJ2aWNlX3NhZmFyaWJvb2tzIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL2VtYWlscyI6WyJtbGVwbGF0cmVAbW96aWxsYS5jb20iLCJtYXRoaWV1QG1vemlsbGEuY29tIiwibWF0aGlldS5sZXBsYXRyZUBtb3ppbGxhLmNvbSJdLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9kbiI6Im1haWw9bWxlcGxhdHJlQG1vemlsbGEuY29tLG89Y29tLGRjPW1vemlsbGEiLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9vcmdhbml6YXRpb25Vbml0cyI6Im1haWw9bWxlcGxhdHJlQG1vemlsbGEuY29tLG89Y29tLGRjPW1vemlsbGEiLCJodHRwczovL3Nzby5tb3ppbGxhLmNvbS9jbGFpbS9lbWFpbF9hbGlhc2VzIjpbIm1hdGhpZXVAbW96aWxsYS5jb20iLCJtYXRoaWV1LmxlcGxhdHJlQG1vemlsbGEuY29tIl0sImh0dHBzOi8vc3NvLm1vemlsbGEuY29tL2NsYWltL19IUkRhdGEiOnsicGxhY2Vob2xkZXIiOiJlbXB0eSJ9fQ.123")
_, err = validator.ValidateRequest(r)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "error in cryptographic primitive")
// Invalid claims
r.Header.Set("Authorization", "Bearer "+goodJWT)
_, err = validator.ValidateRequest(r)
require.NotNil(t, err)
assert.Contains(t, err.Error(), "validation failed, token is expired")
}
func BenchmarkParseKeys(b *testing.B) {
// Warm cache.
validator := newJWTGenericValidator("https://auth.mozilla.auth0.com")
validator.jwks()
b.ResetTimer()
// Bench parsing of cache bytes into keys objects.
for i := 0; i < b.N; i++ {
validator.jwks()
}
}

Просмотреть файл

@ -46,13 +46,13 @@ func VerifyJWTMiddleware(doorman Doorman) gin.HandlerFunc {
}
// No JWT validator configured for this service.
if validator == nil {
// Do nothing.
// Do nothing. The principals list will be empty.
c.Next()
return
}
// Verify the JWT
claims, err := validator.ExtractClaims(c.Request)
claims, err := validator.ValidateRequest(c.Request)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": err.Error(),

Просмотреть файл

@ -23,7 +23,7 @@ func (v *TestValidator) Initialize() error {
args := v.Called()
return args.Error(0)
}
func (v *TestValidator) ExtractClaims(request *http.Request) (*Claims, error) {
func (v *TestValidator) ValidateRequest(request *http.Request) (*Claims, error) {
args := v.Called(request)
return args.Get(0).(*Claims), args.Error(1)
}
@ -45,14 +45,14 @@ func TestJWTMiddleware(t *testing.T) {
Email: "user@corp.com",
Groups: []string{"Employee", "Admins"},
}
v.On("ExtractClaims", mock.Anything).Return(claims, nil)
v.On("ValidateRequest", mock.Anything).Return(claims, nil)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Origin", audience)
handler(c)
v.AssertCalled(t, "ExtractClaims", c.Request)
v.AssertCalled(t, "ValidateRequest", c.Request)
// Principals are set in context.
principals, ok := c.Get(PrincipalsContextKey)
@ -94,7 +94,7 @@ func TestJWTMiddleware(t *testing.T) {
Audience: []string{audience},
}
v = &TestValidator{}
v.On("ExtractClaims", mock.Anything).Return(claims, nil)
v.On("ValidateRequest", mock.Anything).Return(claims, nil)
doorman.jwtValidators[audience] = v
c, _ = gin.CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil)
@ -109,7 +109,7 @@ func TestJWTMiddleware(t *testing.T) {
Audience: []string{"http://some.other.api"},
}
v = &TestValidator{}
v.On("ExtractClaims", mock.Anything).Return(claims, nil)
v.On("ValidateRequest", mock.Anything).Return(claims, nil)
doorman.jwtValidators[audience] = v
c, _ = gin.CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil)