From 9d2a7564450a288f0c059a3e25c044a393e7088d Mon Sep 17 00:00:00 2001 From: Bruno Michel Date: Tue, 10 Oct 2023 15:49:45 +0200 Subject: [PATCH] Update JWT lib to v5 --- go.mod | 3 ++- go.sum | 2 ++ model/bitwarden/oauth.go | 20 ++++++++------- model/instance/instance.go | 7 +++--- model/instance/instance_test.go | 4 +-- model/move/request.go | 2 +- model/oauth/android.go | 2 +- model/oauth/client.go | 18 ++++++------- model/oauth/client_test.go | 6 ++--- model/office/callback.go | 10 +++++--- model/office/open.go | 10 +++++--- model/permission/claims.go | 20 ++++++++++++--- model/session/delegated.go | 2 +- model/session/delegated_test.go | 2 +- model/sharing/oauth.go | 2 +- pkg/crypto/jwt.go | 39 +---------------------------- pkg/crypto/jwt_test.go | 27 ++++++++++---------- web/accounts/oauth.go | 2 +- web/auth/auth_test.go | 12 ++++----- web/data/accounts.go | 2 +- web/files/files.go | 2 +- web/middlewares/permissions.go | 15 +++++++---- web/notes/notes.go | 2 +- web/oidc/oidc.go | 2 +- web/permissions/permissions_test.go | 14 +++++------ worker/exec/konnector_test.go | 6 ++--- 26 files changed, 114 insertions(+), 119 deletions(-) diff --git a/go.mod b/go.mod index 1923001af74..a5627410044 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/gavv/httpexpect/v2 v2.16.0 github.com/gofrs/uuid v4.4.0+incompatible - github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/golang-jwt/jwt/v5 v5.0.0 github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f github.com/goodsign/monday v1.0.1 github.com/google/go-querystring v1.1.0 @@ -67,6 +67,7 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index a25953a21a2..2e2e04215b8 100644 --- a/go.sum +++ b/go.sum @@ -126,6 +126,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg= diff --git a/model/bitwarden/oauth.go b/model/bitwarden/oauth.go index 61ceaa3eb93..d9ca20a0ec6 100644 --- a/model/bitwarden/oauth.go +++ b/model/bitwarden/oauth.go @@ -3,6 +3,7 @@ package bitwarden import ( "strconv" "strings" + "time" "github.com/cozy/cozy-stack/model/bitwarden/settings" "github.com/cozy/cozy-stack/model/instance" @@ -10,6 +11,7 @@ import ( "github.com/cozy/cozy-stack/model/permission" "github.com/cozy/cozy-stack/pkg/consts" "github.com/cozy/cozy-stack/pkg/crypto" + "github.com/golang-jwt/jwt/v5" ) // BitwardenScope is the OAuth scope, and it is hard-coded with the doctypes @@ -93,7 +95,7 @@ func ParseBitwardenDeviceType(deviceType string) (string, string) { // apps. It is an access token, with some additional custom fields. // See https://github.com/bitwarden/jslib/blob/master/common/src/services/token.service.ts func CreateAccessJWT(i *instance.Instance, c *oauth.Client) (string, error) { - now := crypto.Timestamp() + now := time.Now() name, err := i.SettingsPublicName() if err != nil || name == "" { name = "Anonymous" @@ -104,12 +106,12 @@ func CreateAccessJWT(i *instance.Instance, c *oauth.Client) (string, error) { } token, err := crypto.NewJWT(i.OAuthSecret, permission.BitwardenClaims{ Claims: permission.Claims{ - StandardClaims: crypto.StandardClaims{ - Audience: consts.AccessTokenAudience, + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{consts.AccessTokenAudience}, Issuer: i.Domain, - NotBefore: now - 60, - IssuedAt: now, - ExpiresAt: now + int64(consts.AccessTokenValidityDuration.Seconds()), + NotBefore: jwt.NewNumericDate(now.Add(-60 * time.Second)), + IssuedAt: jwt.NewNumericDate(now), + ExpiresAt: jwt.NewNumericDate(now.Add(consts.AccessTokenValidityDuration)), Subject: i.ID(), }, SStamp: stamp, @@ -136,10 +138,10 @@ func CreateRefreshJWT(i *instance.Instance, c *oauth.Client) (string, error) { stamp = settings.SecurityStamp } token, err := crypto.NewJWT(i.OAuthSecret, permission.Claims{ - StandardClaims: crypto.StandardClaims{ - Audience: consts.RefreshTokenAudience, + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{consts.RefreshTokenAudience}, Issuer: i.Domain, - IssuedAt: crypto.Timestamp(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: c.CouchID, }, SStamp: stamp, diff --git a/model/instance/instance.go b/model/instance/instance.go index 632cc470f91..31a96d47439 100644 --- a/model/instance/instance.go +++ b/model/instance/instance.go @@ -24,6 +24,7 @@ import ( "github.com/cozy/cozy-stack/pkg/logger" "github.com/cozy/cozy-stack/pkg/prefixer" "github.com/cozy/cozy-stack/pkg/realtime" + "github.com/golang-jwt/jwt/v5" "github.com/spf13/afero" ) @@ -667,10 +668,10 @@ func (i *Instance) MakeJWT(audience, subject, scope, sessionID string, issuedAt return "", err } return crypto.NewJWT(secret, permission.Claims{ - StandardClaims: crypto.StandardClaims{ - Audience: audience, + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{audience}, Issuer: i.Domain, - IssuedAt: issuedAt.Unix(), + IssuedAt: jwt.NewNumericDate(issuedAt), Subject: subject, }, Scope: scope, diff --git a/model/instance/instance_test.go b/model/instance/instance_test.go index e9d87bcf6fd..4a64abfd02f 100644 --- a/model/instance/instance_test.go +++ b/model/instance/instance_test.go @@ -6,7 +6,7 @@ import ( "github.com/cozy/cozy-stack/model/instance" "github.com/cozy/cozy-stack/pkg/config/config" "github.com/cozy/cozy-stack/pkg/crypto" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" ) @@ -47,7 +47,7 @@ func TestInstance(t *testing.T) { claims, ok := token.Claims.(jwt.MapClaims) assert.True(t, ok, "Claims can be parsed as standard claims") - assert.Equal(t, "app", claims["aud"]) + assert.Equal(t, []interface{}{"app"}, claims["aud"]) assert.Equal(t, "test-ctx-token.example.com", claims["iss"]) assert.Equal(t, "my-app", claims["sub"]) }) diff --git a/model/move/request.go b/model/move/request.go index e4a0d50038f..6fb3e8d3e3e 100644 --- a/model/move/request.go +++ b/model/move/request.go @@ -18,7 +18,7 @@ import ( "github.com/cozy/cozy-stack/pkg/couchdb" "github.com/cozy/cozy-stack/pkg/crypto" "github.com/cozy/cozy-stack/pkg/safehttp" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" multierror "github.com/hashicorp/go-multierror" "github.com/labstack/echo/v4" ) diff --git a/model/oauth/android.go b/model/oauth/android.go index 1ff86e9b352..a23798af3d5 100644 --- a/model/oauth/android.go +++ b/model/oauth/android.go @@ -10,7 +10,7 @@ import ( "github.com/cozy/cozy-stack/model/instance" "github.com/cozy/cozy-stack/pkg/config/config" "github.com/cozy/cozy-stack/pkg/logger" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" ) // checkAndroidAttestation will check an attestation made by the SafetyNet API. diff --git a/model/oauth/client.go b/model/oauth/client.go index 56a803cf150..c9b9fafa53d 100644 --- a/model/oauth/client.go +++ b/model/oauth/client.go @@ -24,7 +24,7 @@ import ( "github.com/cozy/cozy-stack/pkg/metadata" "github.com/cozy/cozy-stack/pkg/registry" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" ) const ( @@ -500,10 +500,10 @@ func (c *Client) Create(i *instance.Instance, opts ...CreateOptions) *ClientRegi } var err error - c.RegistrationToken, err = crypto.NewJWT(i.OAuthSecret, crypto.StandardClaims{ - Audience: consts.RegistrationTokenAudience, + c.RegistrationToken, err = crypto.NewJWT(i.OAuthSecret, jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{consts.RegistrationTokenAudience}, Issuer: i.Domain, - IssuedAt: time.Now().Unix(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: c.CouchID, }) if err != nil { @@ -730,10 +730,10 @@ func (c *Client) AcceptRedirectURI(u string) bool { // CreateJWT returns a new JSON Web Token for the given instance and audience func (c *Client) CreateJWT(i *instance.Instance, audience, scope string) (string, error) { token, err := crypto.NewJWT(i.OAuthSecret, permission.Claims{ - StandardClaims: crypto.StandardClaims{ - Audience: audience, + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{audience}, Issuer: i.Domain, - IssuedAt: crypto.Timestamp(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: c.CouchID, }, Scope: scope, @@ -764,9 +764,9 @@ func validToken(i *instance.Instance, audience, token string) (permission.Claims return claims, false } // Note: the refresh and registration tokens don't expire, no need to check its issue date - if claims.Audience != audience { + if claims.AudienceString() != audience { i.Logger().WithNamespace("oauth"). - Errorf("Unexpected audience for %s token: %s", audience, claims.Audience) + Errorf("Unexpected audience for %s token: %v", audience, claims.Audience) return claims, false } if claims.Issuer != i.Domain { diff --git a/model/oauth/client_test.go b/model/oauth/client_test.go index a5a27140df6..61fa6d3da00 100644 --- a/model/oauth/client_test.go +++ b/model/oauth/client_test.go @@ -14,7 +14,7 @@ import ( "github.com/cozy/cozy-stack/pkg/couchdb/mango" "github.com/cozy/cozy-stack/pkg/metadata" "github.com/cozy/cozy-stack/tests/testutils" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -51,7 +51,7 @@ func TestClient(t *testing.T) { claims, ok := token.Claims.(jwt.MapClaims) assert.True(t, ok, "Claims can be parsed as standard claims") - assert.Equal(t, "test", claims["aud"]) + assert.Equal(t, []interface{}{"test"}, claims["aud"]) assert.Equal(t, testInstance.Domain, claims["iss"]) assert.Equal(t, "my-client-id", claims["sub"]) assert.Equal(t, "foo:read", claims["scope"]) @@ -63,7 +63,7 @@ func TestClient(t *testing.T) { claims, ok := c.ValidToken(testInstance, consts.RefreshTokenAudience, tokenString) assert.True(t, ok, "The token must be valid") - assert.Equal(t, "refresh", claims.Audience) + assert.Equal(t, jwt.ClaimStrings{"refresh"}, claims.Audience) assert.Equal(t, testInstance.Domain, claims.Issuer) assert.Equal(t, "my-client-id", claims.Subject) assert.Equal(t, "foo:read", claims.Scope) diff --git a/model/office/callback.go b/model/office/callback.go index 6c951c9c3c4..0e16fa0b693 100644 --- a/model/office/callback.go +++ b/model/office/callback.go @@ -18,7 +18,7 @@ import ( "github.com/cozy/cozy-stack/pkg/crypto" "github.com/cozy/cozy-stack/pkg/metadata" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" ) // Status list is described on https://api.onlyoffice.com/editors/callback#status @@ -58,8 +58,12 @@ type callbackClaims struct { } `json:"payload"` } -// Valid is part of the jwt.Claims interface -func (c *callbackClaims) Valid() error { return nil } +func (c *callbackClaims) GetExpirationTime() (*jwt.NumericDate, error) { return nil, nil } +func (c *callbackClaims) GetIssuedAt() (*jwt.NumericDate, error) { return nil, nil } +func (c *callbackClaims) GetNotBefore() (*jwt.NumericDate, error) { return nil, nil } +func (c *callbackClaims) GetIssuer() (string, error) { return "", nil } +func (c *callbackClaims) GetSubject() (string, error) { return "", nil } +func (c *callbackClaims) GetAudience() (jwt.ClaimStrings, error) { return nil, nil } // Callback will manage the callback from the document server. func Callback(inst *instance.Instance, params CallbackParameters) error { diff --git a/model/office/open.go b/model/office/open.go index 011f2811493..e0fd0b4abd8 100644 --- a/model/office/open.go +++ b/model/office/open.go @@ -13,7 +13,7 @@ import ( "github.com/cozy/cozy-stack/pkg/consts" "github.com/cozy/cozy-stack/pkg/couchdb" "github.com/cozy/cozy-stack/pkg/jsonapi" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" ) type apiOfficeURL struct { @@ -93,8 +93,12 @@ func (o *apiOfficeURL) sign(cfg *config.Office) (string, error) { return token.SignedString([]byte(cfg.InboxSecret)) } -// Valid is a method of the jwt.Claims interface -func (o *onlyOffice) Valid() error { return nil } +func (o *onlyOffice) GetExpirationTime() (*jwt.NumericDate, error) { return nil, nil } +func (o *onlyOffice) GetIssuedAt() (*jwt.NumericDate, error) { return nil, nil } +func (o *onlyOffice) GetNotBefore() (*jwt.NumericDate, error) { return nil, nil } +func (o *onlyOffice) GetIssuer() (string, error) { return "", nil } +func (o *onlyOffice) GetSubject() (string, error) { return "", nil } +func (o *onlyOffice) GetAudience() (jwt.ClaimStrings, error) { return nil, nil } // Opener can be used to find the parameters for opening an office document. type Opener struct { diff --git a/model/permission/claims.go b/model/permission/claims.go index 6b38af3f1dd..7f85c6d3f80 100644 --- a/model/permission/claims.go +++ b/model/permission/claims.go @@ -4,12 +4,12 @@ import ( "time" "github.com/cozy/cozy-stack/pkg/consts" - "github.com/cozy/cozy-stack/pkg/crypto" + "github.com/golang-jwt/jwt/v5" ) // Claims is used for JWT used in OAuth2 flow and applications token type Claims struct { - crypto.StandardClaims + jwt.RegisteredClaims Scope string `json:"scope,omitempty"` SessionID string `json:"session_id,omitempty"` SStamp string `json:"stamp,omitempty"` @@ -18,13 +18,25 @@ type Claims struct { // IssuedAtUTC returns a time.Time struct of the IssuedAt field in UTC // location. func (claims *Claims) IssuedAtUTC() time.Time { - return time.Unix(claims.IssuedAt, 0).UTC() + return claims.IssuedAt.Time.UTC() +} + +// AudienceString returns the audience as a string. +func (claims *Claims) AudienceString() string { + if len(claims.Audience) == 0 { + return "" + } + return claims.Audience[0] } // Expired returns true if a Claim is expired func (claims *Claims) Expired() bool { + if len(claims.Audience) != 1 { + return true + } + var validityDuration time.Duration - switch claims.Audience { + switch claims.Audience[0] { case consts.AppAudience: if claims.SessionID == "" { // an app token with no session association is used for services which diff --git a/model/session/delegated.go b/model/session/delegated.go index 96af0753c66..a607df98256 100644 --- a/model/session/delegated.go +++ b/model/session/delegated.go @@ -5,7 +5,7 @@ import ( "errors" "time" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/cozy/cozy-stack/model/instance" "github.com/cozy/cozy-stack/pkg/config/config" diff --git a/model/session/delegated_test.go b/model/session/delegated_test.go index 2406e0f8b1c..c5d51dc54bc 100644 --- a/model/session/delegated_test.go +++ b/model/session/delegated_test.go @@ -8,7 +8,7 @@ import ( "github.com/cozy/cozy-stack/model/instance" "github.com/cozy/cozy-stack/pkg/config/config" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" ) diff --git a/model/sharing/oauth.go b/model/sharing/oauth.go index dcf963783d3..ee4541a6342 100644 --- a/model/sharing/oauth.go +++ b/model/sharing/oauth.go @@ -23,7 +23,7 @@ import ( "github.com/cozy/cozy-stack/pkg/couchdb/mango" "github.com/cozy/cozy-stack/pkg/jsonapi" "github.com/cozy/cozy-stack/pkg/safehttp" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" ) diff --git a/pkg/crypto/jwt.go b/pkg/crypto/jwt.go index fa773a9f7e9..e04763b6634 100644 --- a/pkg/crypto/jwt.go +++ b/pkg/crypto/jwt.go @@ -3,47 +3,10 @@ package crypto import ( "errors" "fmt" - "time" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" ) -// StandardClaims are a structured version of the JWT Claims Set, as referenced at -// https://datatracker.ietf.org/doc/html/rfc7519#section-4. They do not follow the -// specification exactly, since they were based on an earlier draft of the -// specification and not updated. The main difference is that they only -// support integer-based date fields and singular audiences. -type StandardClaims struct { - Audience string `json:"aud,omitempty"` - ExpiresAt int64 `json:"exp,omitempty"` - IssuedAt int64 `json:"iat,omitempty"` - Issuer string `json:"iss,omitempty"` - NotBefore int64 `json:"nbf,omitempty"` - Subject string `json:"sub,omitempty"` -} - -// 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 (claims StandardClaims) Valid() error { - now := time.Now().Unix() - - if claims.IssuedAt > now { - return fmt.Errorf("token used before issued") - } - - // 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 claims.ExpiresAt > 0 && claims.ExpiresAt < now { - return fmt.Errorf("token is expired by %v", now-claims.ExpiresAt) - } - if claims.NotBefore > 0 && claims.NotBefore > now { - return fmt.Errorf("token is not valid yet") - } - - return nil -} - // SigningMethod is the algorithm choosed for signing JWT. // Currently, it is HMAC-SHA-512 var SigningMethod = jwt.SigningMethodHS512 diff --git a/pkg/crypto/jwt_test.go b/pkg/crypto/jwt_test.go index 1782472b6d6..7de46324a52 100644 --- a/pkg/crypto/jwt_test.go +++ b/pkg/crypto/jwt_test.go @@ -2,22 +2,23 @@ package crypto import ( "testing" + "time" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" ) type Claims struct { - StandardClaims + jwt.RegisteredClaims Foo string `json:"foo"` } func TestNewJWT(t *testing.T) { secret := GenerateRandomBytes(64) - tokenString, err := NewJWT(secret, StandardClaims{ - Audience: "test", + tokenString, err := NewJWT(secret, jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{"test"}, Issuer: "example.org", - IssuedAt: Timestamp(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: "cozy.io", }) assert.NoError(t, err) @@ -32,7 +33,7 @@ func TestNewJWT(t *testing.T) { claims, ok := token.Claims.(jwt.MapClaims) assert.True(t, ok, "Claims can be parsed as standard claims") - assert.Equal(t, "test", claims["aud"]) + assert.Equal(t, []interface{}{"test"}, claims["aud"]) assert.Equal(t, "example.org", claims["iss"]) assert.Equal(t, "cozy.io", claims["sub"]) } @@ -40,10 +41,10 @@ func TestNewJWT(t *testing.T) { func TestParseJWT(t *testing.T) { secret := GenerateRandomBytes(64) tokenString, err := NewJWT(secret, Claims{ - StandardClaims{ - Audience: "test", + jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{"test"}, Issuer: "example.org", - IssuedAt: Timestamp(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: "cozy.io", }, "bar", @@ -55,7 +56,7 @@ func TestParseJWT(t *testing.T) { return secret, nil }, &claims) assert.NoError(t, err) - assert.Equal(t, "test", claims.Audience) + assert.Equal(t, jwt.ClaimStrings{"test"}, claims.Audience) assert.Equal(t, "example.org", claims.Issuer) assert.Equal(t, "cozy.io", claims.Subject) assert.Equal(t, "bar", claims.Foo) @@ -64,10 +65,10 @@ func TestParseJWT(t *testing.T) { func TestParseInvalidJWT(t *testing.T) { secret := GenerateRandomBytes(64) tokenString, err := NewJWT(secret, Claims{ - StandardClaims{ - Audience: "test", + jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{"test"}, Issuer: "example.org", - IssuedAt: Timestamp(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: "cozy.io", }, "bar", diff --git a/web/accounts/oauth.go b/web/accounts/oauth.go index f1deb47614a..3481eba9fef 100644 --- a/web/accounts/oauth.go +++ b/web/accounts/oauth.go @@ -19,7 +19,7 @@ import ( "github.com/cozy/cozy-stack/web/auth" "github.com/cozy/cozy-stack/web/middlewares" "github.com/cozy/cozy-stack/web/oidc" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" ) diff --git a/web/auth/auth_test.go b/web/auth/auth_test.go index 4b7f4fe3c39..4199df0e04c 100644 --- a/web/auth/auth_test.go +++ b/web/auth/auth_test.go @@ -29,7 +29,7 @@ import ( "github.com/cozy/cozy-stack/web/errors" "github.com/cozy/cozy-stack/web/middlewares" "github.com/gavv/httpexpect/v2" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1309,10 +1309,10 @@ func TestAuth(t *testing.T) { e := testutils.CreateTestClient(t, ts.URL) claims := permission.Claims{ - StandardClaims: crypto.StandardClaims{ - Audience: consts.RefreshTokenAudience, + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{consts.RefreshTokenAudience}, Issuer: domain, - IssuedAt: crypto.Timestamp(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: clientID, }, Scope: "files:write", @@ -2046,7 +2046,7 @@ func TestAuth(t *testing.T) { return testInstance.SessionSecret(), nil }, &claims) assert.NoError(t, err) - assert.Equal(t, consts.KonnectorAudience, claims.Audience) + assert.Equal(t, consts.KonnectorAudience, claims.Audience[0]) assert.Equal(t, domain, claims.Issuer) assert.Equal(t, konnSlug, claims.Subject) assert.Equal(t, "", claims.Scope) @@ -2209,7 +2209,7 @@ func assertValidToken(t *testing.T, testInstance *instance.Instance, token, audi return testInstance.OAuthSecret, nil }, &claims) assert.NoError(t, err) - assert.Equal(t, audience, claims.Audience) + assert.Equal(t, audience, claims.Audience[0]) assert.Equal(t, domain, claims.Issuer) assert.Equal(t, subject, claims.Subject) assert.Equal(t, scope, claims.Scope) diff --git a/web/data/accounts.go b/web/data/accounts.go index 21cb5b6de78..a785f9df6ec 100644 --- a/web/data/accounts.go +++ b/web/data/accounts.go @@ -189,7 +189,7 @@ func CozyMetadataFromClaims(c echo.Context) *metadata.CozyMetadata { var slug, version string if claims := c.Get("claims"); claims != nil { cl := claims.(permission.Claims) - switch cl.Audience { + switch cl.Audience[0] { case consts.AppAudience, consts.KonnectorAudience: slug = cl.Subject case consts.AccessTokenAudience: diff --git a/web/files/files.go b/web/files/files.go index 7410db5bacf..fe65b48496f 100644 --- a/web/files/files.go +++ b/web/files/files.go @@ -2230,7 +2230,7 @@ func CozyMetadataFromClaims(c echo.Context, setUploadFields bool) (*vfs.FilesCoz var client map[string]string if claims := c.Get("claims"); claims != nil { cl := claims.(permission.Claims) - switch cl.Audience { + switch cl.Audience[0] { case consts.AppAudience, consts.KonnectorAudience: slug = cl.Subject case consts.AccessTokenAudience: diff --git a/web/middlewares/permissions.go b/web/middlewares/permissions.go index 9d49040ac4a..3c5f67908a1 100644 --- a/web/middlewares/permissions.go +++ b/web/middlewares/permissions.go @@ -21,7 +21,7 @@ import ( "github.com/cozy/cozy-stack/pkg/couchdb" "github.com/cozy/cozy-stack/pkg/crypto" "github.com/cozy/cozy-stack/pkg/logger" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" ) @@ -139,7 +139,11 @@ func ExtractClaims(c echo.Context, instance *instance.Instance, token string) (* var audience string err := crypto.ParseJWT(token, func(token *jwt.Token) (interface{}, error) { - audience = token.Claims.(*permission.BitwardenClaims).Claims.Audience + audiences := token.Claims.(*permission.BitwardenClaims).Claims.Audience + if len(audiences) != 1 { + return nil, permission.ErrInvalidAudience + } + audience = audiences[0] return instance.PickKey(audience) }, &fullClaims) @@ -230,7 +234,7 @@ func ParseJWT(c echo.Context, instance *instance.Instance, token string) (*permi return nil, err } - switch claims.Audience { + switch claims.AudienceString() { case consts.AccessTokenAudience: if err := instance.MovedError(); err != nil { return nil, err @@ -308,7 +312,8 @@ func ParseJWT(c echo.Context, instance *instance.Instance, token string) (*permi return pdoc, nil default: - return nil, echo.NewHTTPError(http.StatusBadRequest, "Unrecognized token audience "+claims.Audience) + return nil, echo.NewHTTPError(http.StatusBadRequest, + fmt.Sprintf("Unrecognized token audience %v", claims.Audience)) } } @@ -334,7 +339,7 @@ func GetCLIPermission(c echo.Context) (*permission.Permission, bool) { return nil, false } - if claims.Audience == consts.CLIAudience { + if claims.AudienceString() == consts.CLIAudience { if pdoc, err := permission.GetForCLI(claims); err != nil { c.Set(contextPermissionDoc, pdoc) return pdoc, true diff --git a/web/notes/notes.go b/web/notes/notes.go index 69c94baf0e8..0fee1034678 100644 --- a/web/notes/notes.go +++ b/web/notes/notes.go @@ -482,7 +482,7 @@ func wrapError(err error) *jsonapi.Error { func getCreatedBy(c echo.Context) string { if claims, ok := c.Get("claims").(permission.Claims); ok { - switch claims.Audience { + switch claims.Audience[0] { case consts.AppAudience, consts.KonnectorAudience: return claims.Subject } diff --git a/web/oidc/oidc.go b/web/oidc/oidc.go index a7d01b86f71..e1ff8ae8508 100644 --- a/web/oidc/oidc.go +++ b/web/oidc/oidc.go @@ -28,7 +28,7 @@ import ( "github.com/cozy/cozy-stack/web/auth" "github.com/cozy/cozy-stack/web/middlewares" "github.com/cozy/cozy-stack/web/statik" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" ) diff --git a/web/permissions/permissions_test.go b/web/permissions/permissions_test.go index df3bd6adc20..dd929d62fe2 100644 --- a/web/permissions/permissions_test.go +++ b/web/permissions/permissions_test.go @@ -12,11 +12,11 @@ import ( "github.com/cozy/cozy-stack/pkg/config/config" "github.com/cozy/cozy-stack/pkg/consts" "github.com/cozy/cozy-stack/pkg/couchdb" - "github.com/cozy/cozy-stack/pkg/crypto" "github.com/cozy/cozy-stack/tests/testutils" "github.com/cozy/cozy-stack/web/errors" "github.com/cozy/cozy-stack/web/middlewares" "github.com/gavv/httpexpect/v2" + "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -677,10 +677,10 @@ func TestPermissions(t *testing.T) { oauthClient.Create(testInstance) parent, err := middlewares.GetForOauth(testInstance, &permission.Claims{ - StandardClaims: crypto.StandardClaims{ - Audience: consts.AccessTokenAudience, + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{consts.AccessTokenAudience}, Issuer: testInstance.Domain, - IssuedAt: crypto.Timestamp(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: clientID, }, Scope: "@io.cozy.apps/settings", @@ -697,10 +697,10 @@ func TestPermissions(t *testing.T) { ev3, _ := createTestEvent(testInstance) parent, _ := middlewares.GetForOauth(testInstance, &permission.Claims{ - StandardClaims: crypto.StandardClaims{ - Audience: consts.AccessTokenAudience, + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{consts.AccessTokenAudience}, Issuer: testInstance.Domain, - IssuedAt: crypto.Timestamp(), + IssuedAt: jwt.NewNumericDate(time.Now()), Subject: clientID, }, Scope: "io.cozy.events", diff --git a/worker/exec/konnector_test.go b/worker/exec/konnector_test.go index 7f737da7172..3c7ae33a898 100644 --- a/worker/exec/konnector_test.go +++ b/worker/exec/konnector_test.go @@ -22,7 +22,7 @@ import ( "github.com/cozy/cozy-stack/pkg/prefixer" "github.com/cozy/cozy-stack/pkg/realtime" "github.com/cozy/cozy-stack/tests/testutils" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -176,10 +176,10 @@ echo "{\"type\": \"manifest\", \"message\": \"$(ls ${1}/manifest.konnector)\" }" token := msg1[len(cozyURL):] var claims permission.Claims err2 := crypto.ParseJWT(token, func(t *jwt.Token) (interface{}, error) { - return inst.PickKey(t.Claims.(*permission.Claims).Audience) + return inst.PickKey(t.Claims.(*permission.Claims).Audience[0]) }, &claims) assert.NoError(t, err2) - assert.Equal(t, consts.KonnectorAudience, claims.Audience) + assert.Equal(t, consts.KonnectorAudience, claims.Audience[0]) wg.Done() }()