Skip to content

Commit

Permalink
Merge 7fb1354 into cba098b
Browse files Browse the repository at this point in the history
  • Loading branch information
letzya committed Aug 8, 2018
2 parents cba098b + 7fb1354 commit 448ab16
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 28 deletions.
6 changes: 3 additions & 3 deletions apidef/api_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,9 @@ type APIDefinition struct {
JWTIdentityBaseField string `bson:"jwt_identit_base_field" json:"jwt_identity_base_field"`
JWTClientIDBaseField string `bson:"jwt_client_base_field" json:"jwt_client_base_field"`
JWTPolicyFieldName string `bson:"jwt_policy_field_name" json:"jwt_policy_field_name"`
JWTDisableIssuedAtValidation bool `bson:"jwt_disable_issued_at_validation" json:"jwt_disable_issued_at_validation"`
JWTDisableExpiresAtValidation bool `bson:"jwt_disable_expires_at_validation" json:"jwt_disable_expires_at_validation"`
JWTDisableNotBeforeValidation bool `bson:"jwt_disable_not_before_validation" json:"jwt_disable_not_before_validation"`
JWTIssuedAtValidationSkew uint64 `bson:"jwt_issued_at_validation_skew" json:"jwt_issued_at_validation_skew"`
JWTExpiresAtValidationSkew uint64 `bson:"jwt_expires_at_validation_skew" json:"jwt_expires_at_validation_skew"`
JWTNotBeforeValidationSkew uint64 `bson:"jwt_not_before_validation_skew" json:"jwt_not_before_validation_skew"`
JWTSkipKid bool `bson:"jwt_skip_kid" json:"jwt_skip_kid"`
NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"`
EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"`
Expand Down
14 changes: 6 additions & 8 deletions mw_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,20 +487,18 @@ func (k *JWTMiddleware) timeValidateJWTClaims(c jwt.MapClaims) *jwt.ValidationEr
vErr := new(jwt.ValidationError)
now := time.Now().Unix()

// The claims below are optional, by default, so if they are set to the
// default value in Go, let's not fail the verification for them.
if !k.Spec.JWTDisableExpiresAtValidation && c.VerifyExpiresAt(now, false) == false {
vErr.Inner = errors.New("Token is expired")
if !c.VerifyExpiresAt(now-int64(k.Spec.JWTExpiresAtValidationSkew), false) {
vErr.Inner = errors.New("token has expired")
vErr.Errors |= jwt.ValidationErrorExpired
}

if !k.Spec.JWTDisableIssuedAtValidation && c.VerifyIssuedAt(now, false) == false {
vErr.Inner = fmt.Errorf("Token used before issued")
if c.VerifyIssuedAt(now+int64(k.Spec.JWTIssuedAtValidationSkew), false) == false {
vErr.Inner = errors.New("token used before issued")
vErr.Errors |= jwt.ValidationErrorIssuedAt
}

if !k.Spec.JWTDisableNotBeforeValidation && c.VerifyNotBefore(now, false) == false {
vErr.Inner = fmt.Errorf("token is not valid yet")
if c.VerifyNotBefore(now+int64(k.Spec.JWTNotBeforeValidationSkew), false) == false {
vErr.Inner = errors.New("token is not valid yet")
vErr.Errors |= jwt.ValidationErrorNotValidYet
}

Expand Down
216 changes: 199 additions & 17 deletions mw_jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,10 +621,21 @@ func TestJWTSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) {
})
}

func TestJWTSessionInvalidClaims(t *testing.T) {
func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) {
ts := newTykTestServer()
defer ts.Close()

pID := createPolicy()
jwtAuthHeaderGen := func(skew time.Duration) map[string]string {
jwtToken := createJWKToken(func(t *jwt.Token) {
t.Claims.(jwt.MapClaims)["policy_id"] = pID
t.Claims.(jwt.MapClaims)["user_id"] = "user123"
t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(skew).Unix()
})

return map[string]string{"authorization": jwtToken}
}

spec := buildAPI(func(spec *APISpec) {
spec.UseKeylessAccess = false
spec.EnableJWT = true
Expand All @@ -635,39 +646,209 @@ func TestJWTSessionInvalidClaims(t *testing.T) {
spec.Proxy.ListenPath = "/"
})[0]

pID := createPolicy()
// This test is successful by definition
t.Run("Expiry_After_now--Valid_jwt", func(t *testing.T) {
spec.JWTExpiresAtValidationSkew = 0 //Default value
loadAPI(spec)

t.Run("Fail if token expired", func(t *testing.T) {
spec.JWTDisableExpiresAtValidation = false
ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK,
})
})

// This test is successful by definition, so it's true also with skew, but just to avoid confusion.
t.Run("Expiry_After_now-Add_skew--Valid_jwt", func(t *testing.T) {
spec.JWTExpiresAtValidationSkew = 1
loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK,
})
})

t.Run("Expiry_Before_now--Invalid_jwt", func(t *testing.T) {
spec.JWTExpiresAtValidationSkew = 0 //Default value
loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(-time.Second),
Code: http.StatusUnauthorized,
BodyMatch: "Key not authorized: token has expired",
})
})

t.Run("Expired_token-Before_now-Huge_skew--Valid_jwt", func(t *testing.T) {
spec.JWTExpiresAtValidationSkew = 1000 // This value doesn't matter since validation is disabled
loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK,
})
})

t.Run("Expired_token-Before_now-Add_skew--Valid_jwt", func(t *testing.T) {
spec.JWTExpiresAtValidationSkew = 1
loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK,
})
})
}

func TestJWTSessionIssueAtValidationConfigs(t *testing.T) {
ts := newTykTestServer()
defer ts.Close()

pID := createPolicy()
jwtAuthHeaderGen := func(skew time.Duration) map[string]string {
jwtToken := createJWKToken(func(t *jwt.Token) {
t.Claims.(jwt.MapClaims)["policy_id"] = pID
t.Claims.(jwt.MapClaims)["user_id"] = "user"
t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Hour * 72).Unix()
t.Claims.(jwt.MapClaims)["user_id"] = "user123"
t.Claims.(jwt.MapClaims)["iat"] = time.Now().Add(skew).Unix()
})

return map[string]string{"authorization": jwtToken}
}

spec := buildAPI(func(spec *APISpec) {
spec.UseKeylessAccess = false
spec.EnableJWT = true
spec.JWTSigningMethod = "rsa"
spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey))
spec.JWTIdentityBaseField = "user_id"
spec.JWTPolicyFieldName = "policy_id"
spec.Proxy.ListenPath = "/"
})[0]

// This test is successful by definition
t.Run("IssuedAt_Before_now-no_skew--Valid_jwt", func(t *testing.T) {
spec.JWTIssuedAtValidationSkew = 0

loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK,
})
authHeaders := map[string]string{"authorization": jwtToken}
})

t.Run("Expiry_after_now--Invalid_jwt", func(t *testing.T) {
spec.JWTExpiresAtValidationSkew = 0 //Default value

loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: authHeaders,
Code: 401,
BodyMatch: "Key not authorized: Token is expired",
Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK,
})
})

t.Run("Pass if token expired and validation disabled", func(t *testing.T) {
spec.JWTDisableExpiresAtValidation = true
t.Run("IssueAt-After_now-no_skew--Invalid_jwt", func(t *testing.T) {
spec.JWTIssuedAtValidationSkew = 0

loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(+time.Minute),
Code: http.StatusUnauthorized,
BodyMatch: "Key not authorized: token used before issued",
})
})

t.Run("IssueAt--After_now-Huge_skew--valid_jwt", func(t *testing.T) {
spec.JWTIssuedAtValidationSkew = 1000 // This value doesn't matter since validation is disabled
loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(+time.Second),
Code: http.StatusOK,
})
})

// True by definition
t.Run("IssueAt-Before_now-Add_skew--not_valid_jwt", func(t *testing.T) {
spec.JWTIssuedAtValidationSkew = 2 // 2 seconds
loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(-3 * time.Second), Code: http.StatusOK,
})
})

t.Run("IssueAt-After_now-Add_skew--Valid_jwt", func(t *testing.T) {
spec.JWTIssuedAtValidationSkew = 1

loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK,
})
})
}

func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) {
ts := newTykTestServer()
defer ts.Close()

pID := createPolicy()
jwtAuthHeaderGen := func(skew time.Duration) map[string]string {
jwtToken := createJWKToken(func(t *jwt.Token) {
t.Claims.(jwt.MapClaims)["policy_id"] = pID
t.Claims.(jwt.MapClaims)["user_id"] = "user"
t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Hour * 72).Unix()
t.Claims.(jwt.MapClaims)["user_id"] = "user123"
t.Claims.(jwt.MapClaims)["nbf"] = time.Now().Add(skew).Unix()
})
return map[string]string{"authorization": jwtToken}
}

spec := buildAPI(func(spec *APISpec) {
spec.UseKeylessAccess = false
spec.EnableJWT = true
spec.Proxy.ListenPath = "/"
spec.JWTSigningMethod = "rsa"
spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey))
spec.JWTIdentityBaseField = "user_id"
spec.JWTPolicyFieldName = "policy_id"
})[0]

// This test is successful by definition
t.Run("NotBefore_Before_now-Valid_jwt", func(t *testing.T) {
spec.JWTNotBeforeValidationSkew = 0

loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK,
})
authHeaders := map[string]string{"authorization": jwtToken}
})

t.Run("NotBefore_After_now--Invalid_jwt", func(t *testing.T) {
spec.JWTNotBeforeValidationSkew = 0 //Default value

loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: authHeaders, Code: http.StatusOK,
Headers: jwtAuthHeaderGen(+time.Second),
Code: http.StatusUnauthorized,
BodyMatch: "Key not authorized: token is not valid yet",
})
})

t.Run("NotBefore_After_now-Add_skew--valid_jwt", func(t *testing.T) {
spec.JWTNotBeforeValidationSkew = 1

loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK,
})
})

t.Run("NotBefore_After_now-Huge_skew--valid_jwt", func(t *testing.T) {
spec.JWTNotBeforeValidationSkew = 1000 // This value is so high that it's actually similar to disabling the claim.

loadAPI(spec)

ts.Run(t, test.TestCase{
Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK,
})
})
}
Expand Down Expand Up @@ -930,7 +1111,8 @@ func BenchmarkJWTSessionRSAWithEncodedJWK(b *testing.B) {
ts.Run(
b,
test.TestCase{
Headers: authHeaders, Code: http.StatusOK,
Headers: authHeaders,
Code: http.StatusOK,
},
)
}
Expand Down

0 comments on commit 448ab16

Please sign in to comment.