зеркало из https://github.com/mozilla/doorman.git
Split Auth0 code from rest of JWT
This commit is contained in:
Родитель
e4a98e2d2c
Коммит
0e0926677b
|
@ -0,0 +1,45 @@
|
|||
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 {
|
||||
if !strings.HasSuffix(v.Issuer, "auth0.com/") {
|
||||
return fmt.Errorf("issuer %q not supported or has bad format", v.Issuer)
|
||||
}
|
||||
jwksURI := fmt.Sprintf("%s.well-known/jwks.json", v.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, v.Issuer, jose.RS256)
|
||||
v.validator = auth0.NewValidator(config)
|
||||
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
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package doorman
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAuth0Initialize(t *testing.T) {
|
||||
validator := Auth0Validator{"demo.oath-zero.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)
|
||||
}
|
|
@ -22,6 +22,7 @@ func ContextMiddleware(doorman *Doorman) gin.HandlerFunc {
|
|||
func SetupRoutes(r *gin.Engine, doorman *Doorman) {
|
||||
r.Use(ContextMiddleware(doorman))
|
||||
if doorman.JWTIssuer != "" {
|
||||
// XXX: currently only Auth0 is supported.
|
||||
validator := &Auth0Validator{
|
||||
Issuer: doorman.JWTIssuer,
|
||||
}
|
||||
|
|
|
@ -3,12 +3,8 @@ package doorman
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
auth0 "github.com/auth0-community/go-auth0"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
jwt "gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
|
@ -29,40 +25,6 @@ type JWTValidator interface {
|
|||
ExtractClaims(*http.Request) (*Claims, error)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if !strings.HasSuffix(v.Issuer, "auth0.com/") {
|
||||
return fmt.Errorf("issuer %q not supported or has bad format", v.Issuer)
|
||||
}
|
||||
jwksURI := fmt.Sprintf("%s.well-known/jwks.json", v.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, v.Issuer, jose.RS256)
|
||||
v.validator = auth0.NewValidator(config)
|
||||
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
|
||||
}
|
||||
|
||||
// VerifyJWTMiddleware makes sure a valid JWT is provided.
|
||||
func VerifyJWTMiddleware(validator JWTValidator) gin.HandlerFunc {
|
||||
validator.Initialize()
|
||||
|
@ -99,11 +61,13 @@ func VerifyJWTMiddleware(validator JWTValidator) gin.HandlerFunc {
|
|||
var principals Principals
|
||||
userid := fmt.Sprintf("userid:%s", claims.Subject)
|
||||
principals = append(principals, userid)
|
||||
|
||||
// Main email (no alias)
|
||||
if claims.Email != "" {
|
||||
email := fmt.Sprintf("email:%s", claims.Email)
|
||||
principals = append(principals, email)
|
||||
}
|
||||
|
||||
// Groups
|
||||
for _, group := range claims.Groups {
|
||||
prefixed := fmt.Sprintf("group:%s", group)
|
||||
|
|
|
@ -15,40 +15,6 @@ import (
|
|||
// TestMain defined in doorman_test.go
|
||||
// func TestMain(m *testing.M) {}
|
||||
|
||||
func TestAuth0Initialize(t *testing.T) {
|
||||
validator := Auth0Validator{"demo.oath-zero.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)
|
||||
}
|
||||
|
||||
type TestValidator struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче