diff --git a/doorman/auth0.go b/doorman/auth0.go new file mode 100644 index 0000000..8976838 --- /dev/null +++ b/doorman/auth0.go @@ -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 +} diff --git a/doorman/auth0_test.go b/doorman/auth0_test.go new file mode 100644 index 0000000..0764a08 --- /dev/null +++ b/doorman/auth0_test.go @@ -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) +} diff --git a/doorman/handler.go b/doorman/handler.go index 0aa6446..6872191 100644 --- a/doorman/handler.go +++ b/doorman/handler.go @@ -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, } diff --git a/doorman/jwt.go b/doorman/jwt.go index 4b91e64..14b3d0f 100644 --- a/doorman/jwt.go +++ b/doorman/jwt.go @@ -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) diff --git a/doorman/jwt_test.go b/doorman/jwt_test.go index 23f038c..10162fc 100644 --- a/doorman/jwt_test.go +++ b/doorman/jwt_test.go @@ -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 }