Skip to content

Commit

Permalink
Merge pull request #112 from iegomez/feat/per-check-jwt-skip-expiration
Browse files Browse the repository at this point in the history
Allow to skip expiration per user or acl check in JWT.
  • Loading branch information
iegomez authored Oct 30, 2020
2 parents 33ab134 + 283a280 commit 3bc6508
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 34 deletions.
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -785,18 +785,19 @@ auth_opt_jwt_remote true

The following `auth_opt_` options are supported by the `jwt` backend when remote is set to true:

| Option | default | Mandatory | Meaning |
| --------------------- | ----------------- | :---------: | ---------------------------------- |
| jwt_host | | Y | API server host name or ip |
| jwt_port | | Y | TCP port number |
| jwt_getuser_uri | | Y | URI for check username/password |
| jwt_superuser_uri | | N | URI for check superuser |
| jwt_aclcheck_uri | | Y | URI for check acl |
| jwt_with_tls | false | N | Use TLS on connect |
| jwt_verify_peer | false | N | Whether to verify peer for tls |
| jwt_skip_expiration | false | N | Skip token expiration |
| jwt_response_mode | status | N | Response type (status, json, text) |
| jwt_params_mode | json | N | Data type (json, form) |
| Option | default | Mandatory | Meaning |
| ------------------------- | ----------------- | :---------: | ------------------------------------------- |
| jwt_host | | Y | API server host name or ip |
| jwt_port | | Y | TCP port number |
| jwt_getuser_uri | | Y | URI for check username/password |
| jwt_superuser_uri | | N | URI for check superuser |
| jwt_aclcheck_uri | | Y | URI for check acl |
| jwt_with_tls | false | N | Use TLS on connect |
| jwt_verify_peer | false | N | Whether to verify peer for tls |
| jwt_skip_user_expiration | false | N | Skip token expiration: (super)user check) |
| jwt_skip_acl_expiration | false | N | Skip token expiration: acl check |
| jwt_response_mode | status | N | Response type (status, json, text) |
| jwt_params_mode | json | N | Data type (json, form) |


URIs (like jwt_getuser_uri) are expected to be in the form `/path`. For example, if jwt_with_tls is `false`, jwt_host is `localhost`, jwt_port `3000` and jwt_getuser_uri is `/user`, mosquitto will send a POST request to `http://localhost:3000/user` to get a response to check against. How data is sent (either json encoded or as form values) and received (as a simple http status code, a json encoded response or plain text), is given by options jwt_response_mode and jwt_params_mode.
Expand Down
35 changes: 20 additions & 15 deletions backends/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ type JWT struct {
SuperuserQuery string
AclQuery string

UserUri string
SuperuserUri string
AclUri string
Host string
Port string
WithTLS bool
VerifyPeer bool
SkipExpiration bool
UserUri string
SuperuserUri string
AclUri string
Host string
Port string
WithTLS bool
VerifyPeer bool
SkipUserExpiration bool
SkipACLExpiration bool

ParamsMode string
ResponseMode string
Expand Down Expand Up @@ -88,8 +89,12 @@ func NewJWT(authOpts map[string]string, logLevel log.Level, hasher hashing.HashC
jwt.Remote = true
}

if skipExpiration, ok := authOpts["jwt_skip_expiration"]; ok && skipExpiration == "true" {
jwt.SkipExpiration = true
if skipUserExpiration, ok := authOpts["jwt_skip_user_expiration"]; ok && skipUserExpiration == "true" {
jwt.SkipUserExpiration = true
}

if skipACLExpiration, ok := authOpts["jwt_skip_acl_expiration"]; ok && skipACLExpiration == "true" {
jwt.SkipACLExpiration = true
}

//If remote, set remote api fields. Else, set jwt secret.
Expand Down Expand Up @@ -236,7 +241,7 @@ func (o JWT) GetUser(token, password, clientid string) bool {
}

//If not remote, get the claims and check against postgres for user.
claims, err := o.getClaims(token)
claims, err := o.getClaims(token, o.SkipUserExpiration)

if err != nil {
log.Printf("jwt get user error: %s", err)
Expand Down Expand Up @@ -266,7 +271,7 @@ func (o JWT) GetSuperuser(token string) bool {
if o.SuperuserQuery == "" {
return false
}
claims, err := o.getClaims(token)
claims, err := o.getClaims(token, o.SkipUserExpiration)

if err != nil {
log.Debugf("jwt get superuser error: %s", err)
Expand Down Expand Up @@ -311,7 +316,7 @@ func (o JWT) CheckAcl(token, topic, clientid string, acc int32) bool {
if o.AclQuery == "" {
return true
}
claims, err := o.getClaims(token)
claims, err := o.getClaims(token, o.SkipACLExpiration)

if err != nil {
log.Debugf("jwt check acl error: %s", err)
Expand Down Expand Up @@ -472,15 +477,15 @@ func (o JWT) getLocalUser(username string) bool {
return false
}

func (o JWT) getClaims(tokenStr string) (*Claims, error) {
func (o JWT) getClaims(tokenStr string, skipExpiration bool) (*Claims, error) {

jwtToken, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(o.Secret), nil
})

expirationError := false
if err != nil {
if !o.SkipExpiration {
if !skipExpiration {
log.Debugf("jwt parse error: %s", err)
return nil, err
}
Expand Down
12 changes: 5 additions & 7 deletions backends/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,44 +84,42 @@ func TestJWTClaims(t *testing.T) {
token, err := jwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
_, err = jwt.getClaims(token, false)
So(err, ShouldBeNil)
})

Convey("A token signed with a different secret should give an error", func() {
token, err := jwtToken.SignedString([]byte("wrong-secret"))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
_, err = jwt.getClaims(token, false)
So(err, ShouldNotBeNil)
})

Convey("Wrong user token should give no error", func() {
token, err := wrongJwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
_, err = jwt.getClaims(token, false)
So(err, ShouldBeNil)
})

Convey("Expired token should give an error when getting claims", func() {
token, err := expiredToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
_, err = jwt.getClaims(token, false)
So(err, ShouldNotBeNil)
})

Convey("When setting skip expiration, expired token should not give an error", func() {
authOpts["jwt_skip_expiration"] = "true"

jwt, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""))
So(err, ShouldBeNil)

token, err := expiredToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)

_, err = jwt.getClaims(token)
_, err = jwt.getClaims(token, true)
So(err, ShouldBeNil)
})
})
Expand Down

0 comments on commit 3bc6508

Please sign in to comment.