From 64c4dc66f31a5b406119a8cd11e208b89da0ffc0 Mon Sep 17 00:00:00 2001 From: Kolawole Segun Date: Sun, 13 Feb 2022 14:10:13 -0600 Subject: [PATCH] convert validatior to functional style --- claims.go | 68 +++++++++++++++++++++------------------------ map_claims.go | 37 ++++++++++++------------ parser.go | 2 +- parser_option.go | 2 +- validator_option.go | 30 +++++++------------- 5 files changed, 60 insertions(+), 79 deletions(-) diff --git a/claims.go b/claims.go index 585fce01..5f419cb4 100644 --- a/claims.go +++ b/claims.go @@ -9,7 +9,7 @@ import ( // Claims must just have a Valid method that determines // if the token is invalid for any supported reason type Claims interface { - Valid(options ...*ValidatorOptions) error + Valid(options ...ValidatorOption) error } // RegisteredClaims are a structured version of the JWT Claims Set, @@ -48,14 +48,13 @@ type RegisteredClaims struct { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c RegisteredClaims) Valid(opts ...*ValidatorOptions) error { +func (c RegisteredClaims) Valid(opts ...ValidatorOption) error { vErr := new(ValidationError) now := TimeFunc() - o := MergeValidatorOptions(opts...) // 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 !c.VerifyExpiresAt(now, false, o) { + if !c.VerifyExpiresAt(now, false, opts...) { delta := now.Sub(c.ExpiresAt.Time) vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired) vErr.Errors |= ValidationErrorExpired @@ -66,7 +65,7 @@ func (c RegisteredClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !c.VerifyNotBefore(now, false, o) { + if !c.VerifyNotBefore(now, false, opts...) { vErr.Inner = ErrTokenNotValidYet vErr.Errors |= ValidationErrorNotValidYet } @@ -86,17 +85,16 @@ func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...*ValidatorOptions) bool { - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway +func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool, opts ...ValidatorOption) bool { + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } if c.ExpiresAt == nil { - return verifyExp(nil, cmp, req, s) + return verifyExp(nil, cmp, req, validator.leeway) } - return verifyExp(&c.ExpiresAt.Time, cmp, req, s) + return verifyExp(&c.ExpiresAt.Time, cmp, req, validator.leeway) } // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat). @@ -111,17 +109,16 @@ func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...*ValidatorOptions) bool { - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway +func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool, opts ...ValidatorOption) bool { + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } if c.NotBefore == nil { - return verifyNbf(nil, cmp, req, s) + return verifyNbf(nil, cmp, req, validator.leeway) } - return verifyNbf(&c.NotBefore.Time, cmp, req, s) + return verifyNbf(&c.NotBefore.Time, cmp, req, validator.leeway) } // VerifyIssuer compares the iss claim against cmp. @@ -152,14 +149,13 @@ type StandardClaims struct { // Valid validates time based claims "exp, iat, nbf". There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (c StandardClaims) Valid(opts ...*ValidatorOptions) error { +func (c StandardClaims) Valid(opts ...ValidatorOption) error { vErr := new(ValidationError) now := TimeFunc().Unix() - o := MergeValidatorOptions(opts...) // 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 !c.VerifyExpiresAt(now, false, o) { + if !c.VerifyExpiresAt(now, false, opts...) { delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired) vErr.Errors |= ValidationErrorExpired @@ -170,7 +166,7 @@ func (c StandardClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !c.VerifyNotBefore(now, false, o) { + if !c.VerifyNotBefore(now, false, opts...) { vErr.Inner = ErrTokenNotValidYet vErr.Errors |= ValidationErrorNotValidYet } @@ -190,18 +186,17 @@ func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. -func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOptions) bool { - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway +func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool, opts ...ValidatorOption) bool { + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } if c.ExpiresAt == 0 { - return verifyExp(nil, time.Unix(cmp, 0), req, s) + return verifyExp(nil, time.Unix(cmp, 0), req, validator.leeway) } t := time.Unix(c.ExpiresAt, 0) - return verifyExp(&t, time.Unix(cmp, 0), req, s) + return verifyExp(&t, time.Unix(cmp, 0), req, validator.leeway) } // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat). @@ -217,18 +212,17 @@ func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOptions) bool { - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway +func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool, opts ...ValidatorOption) bool { + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } if c.NotBefore == 0 { - return verifyNbf(nil, time.Unix(cmp, 0), req, s) + return verifyNbf(nil, time.Unix(cmp, 0), req, validator.leeway) } t := time.Unix(c.NotBefore, 0) - return verifyNbf(&t, time.Unix(cmp, 0), req, s) + return verifyNbf(&t, time.Unix(cmp, 0), req, validator.leeway) } // VerifyIssuer compares the iss claim against cmp. diff --git a/map_claims.go b/map_claims.go index b106fe74..7be34882 100644 --- a/map_claims.go +++ b/map_claims.go @@ -34,7 +34,7 @@ func (m MapClaims) VerifyAudience(cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp <= exp). // If req is false, it will return true, if exp is unset. -func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOptions) bool { +func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...ValidatorOption) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["exp"] @@ -42,23 +42,22 @@ func (m MapClaims) VerifyExpiresAt(cmp int64, req bool, opts ...*ValidatorOption return !req } - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } switch exp := v.(type) { case float64: if exp == 0 { - return verifyExp(nil, cmpTime, req, s) + return verifyExp(nil, cmpTime, req, validator.leeway) } - return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req, s) + return verifyExp(&newNumericDateFromSeconds(exp).Time, cmpTime, req, validator.leeway) case json.Number: v, _ := exp.Float64() - return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req, s) + return verifyExp(&newNumericDateFromSeconds(v).Time, cmpTime, req, validator.leeway) } return false @@ -92,7 +91,7 @@ func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf). // If req is false, it will return true, if nbf is unset. -func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOptions) bool { +func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...ValidatorOption) bool { cmpTime := time.Unix(cmp, 0) v, ok := m["nbf"] @@ -100,23 +99,22 @@ func (m MapClaims) VerifyNotBefore(cmp int64, req bool, opts ...*ValidatorOption return !req } - var s time.Duration - o := MergeValidatorOptions(opts...) - if o != nil { - s = o.leeway + validator := ValidatorOptions{} + for _, o := range opts { + o(&validator) } switch nbf := v.(type) { case float64: if nbf == 0 { - return verifyNbf(nil, cmpTime, req, s) + return verifyNbf(nil, cmpTime, req, validator.leeway) } - return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req, s) + return verifyNbf(&newNumericDateFromSeconds(nbf).Time, cmpTime, req, validator.leeway) case json.Number: v, _ := nbf.Float64() - return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req, s) + return verifyNbf(&newNumericDateFromSeconds(v).Time, cmpTime, req, validator.leeway) } return false @@ -133,12 +131,11 @@ func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { // There is no accounting for clock skew. // As well, if any of the above claims are not in the token, it will still // be considered a valid claim. -func (m MapClaims) Valid(opts ...*ValidatorOptions) error { +func (m MapClaims) Valid(opts ...ValidatorOption) error { vErr := new(ValidationError) now := TimeFunc().Unix() - o := MergeValidatorOptions(opts...) - if !m.VerifyExpiresAt(now, false, o) { + if !m.VerifyExpiresAt(now, false, opts...) { // TODO(oxisto): this should be replaced with ErrTokenExpired vErr.Inner = errors.New("Token is expired") vErr.Errors |= ValidationErrorExpired @@ -150,7 +147,7 @@ func (m MapClaims) Valid(opts ...*ValidatorOptions) error { vErr.Errors |= ValidationErrorIssuedAt } - if !m.VerifyNotBefore(now, false, o) { + if !m.VerifyNotBefore(now, false, opts...) { // TODO(oxisto): this should be replaced with ErrTokenNotValidYet vErr.Inner = errors.New("Token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet diff --git a/parser.go b/parser.go index c1248bf8..7e97fe52 100644 --- a/parser.go +++ b/parser.go @@ -23,7 +23,7 @@ type Parser struct { // Deprecated: In future releases, this field will not be exported anymore and should be set with an option to NewParser instead. SkipClaimsValidation bool - options []*ValidatorOptions + options []ValidatorOption } // NewParser creates a new Parser with the specified options diff --git a/parser_option.go b/parser_option.go index aa454009..e00f44d4 100644 --- a/parser_option.go +++ b/parser_option.go @@ -33,6 +33,6 @@ func WithoutClaimsValidation() ParserOption { // WithLeeway returns the ParserOption for specifying the leeway window. func WithLeeway(d time.Duration) ParserOption { return func(p *Parser) { - p.options = append(p.options, &ValidatorOptions{leeway: d}) + p.options = append(p.options, WithLeewayValidator(d)) } } diff --git a/validator_option.go b/validator_option.go index 2ee80c47..56f7e4b1 100644 --- a/validator_option.go +++ b/validator_option.go @@ -2,30 +2,20 @@ package jwt import "time" +// ValidatorOption is used to implement functional-style options that modify the behavior of the parser. To add +// new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that +// takes a *ValidatorOptions type as input and manipulates its configuration accordingly. +type ValidatorOption func(*ValidatorOptions) + // ValidatorOptions represents options that can be used for claims validation type ValidatorOptions struct { - leeway time.Duration // Leeway to provide when validating time values -} - -func Validator() *ValidatorOptions { - return &ValidatorOptions{} + leeway time.Duration // Leeway to provide when validating time values } -func (v *ValidatorOptions) SetLeeway(d time.Duration) { - v.leeway = d -} -// MergeValidatorOptions combines the given ValidatorOptions instances into a single ValidatorOptions -// in a last-one-wins fashion -func MergeValidatorOptions(opts ...*ValidatorOptions) *ValidatorOptions { - v := Validator() - for _, opt := range opts { - if opt == nil { - continue - } - if opt.leeway != 0 { - v.SetLeeway(opt.leeway) - } +// WithLeewayValidator is an option to set the clock skew (leeway) windows +func WithLeewayValidator(d time.Duration) ValidatorOption { + return func(v *ValidatorOptions) { + v.leeway = d } - return v }