Skip to content

Commit 3465403

Browse files
authored
update to jwt v5 (crewjam#614)
* update to jwt v5 * chore * chore * fix feedback
1 parent 4664c86 commit 3465403

File tree

8 files changed

+102
-116
lines changed

8 files changed

+102
-116
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ module github.com/crewjam/saml
33
go 1.22
44

55
require (
6+
github.com/golang-jwt/jwt/v5 v5.2.2
67
github.com/beevik/etree v1.5.0
7-
github.com/golang-jwt/jwt/v4 v4.5.2
88
github.com/google/go-cmp v0.7.0
99
github.com/mattermost/xml-roundtrip-validator v0.1.0
1010
github.com/russellhaering/goxmldsig v1.4.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
55
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
66
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
77
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8-
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
9-
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
8+
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
9+
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
1010
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
1111
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
1212
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=

identity_provider_test.go

Lines changed: 75 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"gotest.tools/golden"
2626

2727
"github.com/beevik/etree"
28-
"github.com/golang-jwt/jwt/v4"
2928
dsig "github.com/russellhaering/goxmldsig"
3029

3130
"github.com/crewjam/saml/logger"
@@ -104,7 +103,6 @@ func NewIdentityProviderTest(t *testing.T, opts ...idpTestOpts) *IdentityProvide
104103
rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015")
105104
return rv
106105
}
107-
jwt.TimeFunc = TimeNow
108106
RandReader = &testRandomReader{} // TODO(ross): remove this and use the below generator
109107
xmlenc.RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests
110108

@@ -485,7 +483,6 @@ func TestIDPCanValidate(t *testing.T) {
485483
"</AuthnRequest>"),
486484
}
487485
assert.Check(t, is.Error(req.Validate(), "cannot find assertion consumer service: file does not exist"))
488-
489486
}
490487

491488
func TestIDPMakeAssertion(t *testing.T) {
@@ -592,94 +589,93 @@ func TestIDPMakeAssertion(t *testing.T) {
592589
})
593590
assert.Check(t, err)
594591

595-
expectedAttributes :=
596-
[]Attribute{
597-
{
598-
FriendlyName: "uid",
599-
Name: "urn:oid:0.9.2342.19200300.100.1.1",
600-
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
601-
Values: []AttributeValue{
602-
{
603-
Type: "xs:string",
604-
Value: "alice",
605-
},
592+
expectedAttributes := []Attribute{
593+
{
594+
FriendlyName: "uid",
595+
Name: "urn:oid:0.9.2342.19200300.100.1.1",
596+
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
597+
Values: []AttributeValue{
598+
{
599+
Type: "xs:string",
600+
Value: "alice",
606601
},
607602
},
608-
{
609-
FriendlyName: "mail",
610-
Name: "urn:oid:0.9.2342.19200300.100.1.3",
611-
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
612-
Values: []AttributeValue{
613-
{
614-
Type: "xs:string",
615-
616-
},
603+
},
604+
{
605+
FriendlyName: "mail",
606+
Name: "urn:oid:0.9.2342.19200300.100.1.3",
607+
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
608+
Values: []AttributeValue{
609+
{
610+
Type: "xs:string",
611+
617612
},
618613
},
619-
{
620-
FriendlyName: "eduPersonPrincipalName",
621-
Name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
622-
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
623-
Values: []AttributeValue{
624-
{
625-
Type: "xs:string",
626-
627-
},
614+
},
615+
{
616+
FriendlyName: "eduPersonPrincipalName",
617+
Name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
618+
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
619+
Values: []AttributeValue{
620+
{
621+
Type: "xs:string",
622+
628623
},
629624
},
630-
{
631-
FriendlyName: "sn",
632-
Name: "urn:oid:2.5.4.4",
633-
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
634-
Values: []AttributeValue{
635-
{
636-
Type: "xs:string",
637-
Value: "Smith",
638-
},
625+
},
626+
{
627+
FriendlyName: "sn",
628+
Name: "urn:oid:2.5.4.4",
629+
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
630+
Values: []AttributeValue{
631+
{
632+
Type: "xs:string",
633+
Value: "Smith",
639634
},
640635
},
641-
{
642-
FriendlyName: "givenName",
643-
Name: "urn:oid:2.5.4.42",
644-
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
645-
Values: []AttributeValue{
646-
{
647-
Type: "xs:string",
648-
Value: "Alice",
649-
},
636+
},
637+
{
638+
FriendlyName: "givenName",
639+
Name: "urn:oid:2.5.4.42",
640+
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
641+
Values: []AttributeValue{
642+
{
643+
Type: "xs:string",
644+
Value: "Alice",
650645
},
651646
},
652-
{
653-
FriendlyName: "cn",
654-
Name: "urn:oid:2.5.4.3",
655-
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
656-
Values: []AttributeValue{
657-
{
658-
Type: "xs:string",
659-
Value: "Alice Smith",
660-
},
647+
},
648+
{
649+
FriendlyName: "cn",
650+
Name: "urn:oid:2.5.4.3",
651+
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
652+
Values: []AttributeValue{
653+
{
654+
Type: "xs:string",
655+
Value: "Alice Smith",
661656
},
662657
},
663-
{
664-
FriendlyName: "eduPersonAffiliation",
665-
Name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.1",
666-
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
667-
Values: []AttributeValue{
668-
{
669-
Type: "xs:string",
670-
Value: "Users",
671-
},
672-
{
673-
Type: "xs:string",
674-
Value: "Administrators",
675-
},
676-
{
677-
Type: "xs:string",
678-
Value: "♀",
679-
},
658+
},
659+
{
660+
FriendlyName: "eduPersonAffiliation",
661+
Name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.1",
662+
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
663+
Values: []AttributeValue{
664+
{
665+
Type: "xs:string",
666+
Value: "Users",
667+
},
668+
{
669+
Type: "xs:string",
670+
Value: "Administrators",
671+
},
672+
{
673+
Type: "xs:string",
674+
Value: "♀",
680675
},
681676
},
682-
}
677+
},
678+
}
683679
assert.Check(t, is.DeepEqual(expectedAttributes, req.Assertion.AttributeStatements[0].Attributes))
684680
}
685681

@@ -1044,7 +1040,8 @@ func TestIDPRequestedAttributes(t *testing.T) {
10441040
},
10451041
},
10461042
},
1047-
}}}
1043+
},
1044+
}}
10481045
assert.Check(t, is.DeepEqual(expectedAttributes, req.Assertion.AttributeStatements))
10491046
}
10501047

samlidp/samlidp_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import (
1616
is "gotest.tools/assert/cmp"
1717
"gotest.tools/golden"
1818

19-
"github.com/golang-jwt/jwt/v4"
20-
2119
"github.com/crewjam/saml"
2220
"github.com/crewjam/saml/logger"
2321
)
@@ -83,7 +81,6 @@ func NewServerTest(t *testing.T) *ServerTest {
8381
rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015")
8482
return rv
8583
}
86-
jwt.TimeFunc = saml.TimeNow
8784
saml.RandReader = &testRandomReader{}
8885

8986
test.SPKey = mustParsePrivateKey(golden.Get(t, "sp_key.pem")).(*rsa.PrivateKey)

samlsp/middleware_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"testing"
1818
"time"
1919

20-
"github.com/golang-jwt/jwt/v4"
2120
dsig "github.com/russellhaering/goxmldsig"
2221
"gotest.tools/assert"
2322
is "gotest.tools/assert/cmp"
@@ -55,7 +54,6 @@ func NewMiddlewareTest(t *testing.T) *MiddlewareTest {
5554
rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 MST 2006", "Mon Dec 1 01:57:09.123456789 UTC 2015")
5655
return rv
5756
}
58-
jwt.TimeFunc = saml.TimeNow
5957
saml.Clock = dsig.NewFakeClockAt(saml.TimeNow())
6058
saml.RandReader = &testRandomReader{}
6159

@@ -281,7 +279,7 @@ func TestMiddlewareRequireAccountBadCreds(t *testing.T) {
281279

282280
func TestMiddlewareRequireAccountExpiredCreds(t *testing.T) {
283281
test := NewMiddlewareTest(t)
284-
jwt.TimeFunc = func() time.Time {
282+
saml.TimeNow = func() time.Time {
285283
rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Mon Dec 1 01:31:21 UTC 2115")
286284
return rv
287285
}
@@ -306,7 +304,7 @@ func TestMiddlewareRequireAccountExpiredCreds(t *testing.T) {
306304
assert.Check(t, err)
307305
decodedRequest, err := testsaml.ParseRedirectRequest(redirectURL)
308306
assert.Check(t, err)
309-
golden.Assert(t, string(decodedRequest), "expected_authn_request_secure.xml")
307+
golden.Assert(t, strings.Replace(string(decodedRequest), `IssueInstant="2115-12-01T01:31:21Z"`, `IssueInstant="2015-12-01T01:57:09.123Z"`, 1), "expected_authn_request_secure.xml")
310308
}
311309

312310
func TestMiddlewareRequireAccountPanicOnRequestToACS(t *testing.T) {
@@ -411,7 +409,8 @@ func TestMiddlewareCanParseResponse(t *testing.T) {
411409
assert.Check(t, is.DeepEqual([]string{
412410
"saml_KCosLjAyNDY4Ojw-QEJERkhKTE5QUlRWWFpcXmBiZGZoamxucHJ0dnh6=; Path=/saml2/acs; Domain=15661444.ngrok.io; Expires=Thu, 01 Jan 1970 00:00:01 GMT",
413411
"ttt=" + test.expectedSessionCookie + "; " +
414-
"Path=/; Domain=15661444.ngrok.io; Max-Age=7200; HttpOnly; Secure"},
412+
"Path=/; Domain=15661444.ngrok.io; Max-Age=7200; HttpOnly; Secure",
413+
},
415414
resp.Header()["Set-Cookie"]))
416415
}
417416

samlsp/new.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"net/http"
1111
"net/url"
1212

13-
"github.com/golang-jwt/jwt/v4"
13+
"github.com/golang-jwt/jwt/v5"
1414
dsig "github.com/russellhaering/goxmldsig"
1515

1616
"github.com/crewjam/saml"

samlsp/request_tracker_jwt.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"fmt"
66
"time"
77

8-
"github.com/golang-jwt/jwt/v4"
8+
"github.com/golang-jwt/jwt/v5"
99

1010
"github.com/crewjam/saml"
1111
)
@@ -49,22 +49,19 @@ func (s JWTTrackedRequestCodec) Encode(value TrackedRequest) (string, error) {
4949

5050
// Decode returns a Tracked request from an encoded string.
5151
func (s JWTTrackedRequestCodec) Decode(signed string) (*TrackedRequest, error) {
52-
parser := jwt.Parser{
53-
ValidMethods: []string{s.SigningMethod.Alg()},
54-
}
52+
parser := jwt.NewParser(
53+
jwt.WithValidMethods([]string{s.SigningMethod.Alg()}),
54+
jwt.WithTimeFunc(saml.TimeNow),
55+
jwt.WithAudience(s.Audience),
56+
jwt.WithIssuer(s.Issuer),
57+
)
5558
claims := JWTTrackedRequestClaims{}
5659
_, err := parser.ParseWithClaims(signed, &claims, func(*jwt.Token) (interface{}, error) {
5760
return s.Key.Public(), nil
5861
})
5962
if err != nil {
6063
return nil, err
6164
}
62-
if !claims.VerifyAudience(s.Audience, true) {
63-
return nil, fmt.Errorf("expected audience %q, got %q", s.Audience, claims.Audience)
64-
}
65-
if !claims.VerifyIssuer(s.Issuer, true) {
66-
return nil, fmt.Errorf("expected issuer %q, got %q", s.Issuer, claims.Issuer)
67-
}
6865
if !claims.SAMLAuthnRequest {
6966
return nil, fmt.Errorf("expected saml-authn-request")
7067
}

samlsp/session_jwt.go

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ package samlsp
33
import (
44
"crypto"
55
"errors"
6-
"fmt"
76
"time"
87

9-
"github.com/golang-jwt/jwt/v4"
8+
"github.com/golang-jwt/jwt/v5"
109

1110
"github.com/crewjam/saml"
1211
)
@@ -35,11 +34,11 @@ func (c JWTSessionCodec) New(assertion *saml.Assertion) (Session, error) {
3534
now := saml.TimeNow()
3635
claims := JWTSessionClaims{}
3736
claims.SAMLSession = true
38-
claims.Audience = c.Audience
37+
claims.Audience = jwt.ClaimStrings{c.Audience}
3938
claims.Issuer = c.Issuer
40-
claims.IssuedAt = now.Unix()
41-
claims.ExpiresAt = now.Add(c.MaxAge).Unix()
42-
claims.NotBefore = now.Unix()
39+
claims.IssuedAt = jwt.NewNumericDate(now)
40+
claims.ExpiresAt = jwt.NewNumericDate(now.Add(c.MaxAge))
41+
claims.NotBefore = jwt.NewNumericDate(now)
4342

4443
if sub := assertion.Subject; sub != nil {
4544
if nameID := sub.NameID; nameID != nil {
@@ -89,9 +88,12 @@ func (c JWTSessionCodec) Encode(s Session) (string, error) {
8988
// Decode parses the serialized session that may have been returned by Encode
9089
// and returns a Session.
9190
func (c JWTSessionCodec) Decode(signed string) (Session, error) {
92-
parser := jwt.Parser{
93-
ValidMethods: []string{c.SigningMethod.Alg()},
94-
}
91+
parser := jwt.NewParser(
92+
jwt.WithValidMethods([]string{c.SigningMethod.Alg()}),
93+
jwt.WithTimeFunc(saml.TimeNow),
94+
jwt.WithAudience(c.Audience),
95+
jwt.WithIssuer(c.Issuer),
96+
)
9597
claims := JWTSessionClaims{}
9698
_, err := parser.ParseWithClaims(signed, &claims, func(*jwt.Token) (interface{}, error) {
9799
return c.Key.Public(), nil
@@ -100,12 +102,6 @@ func (c JWTSessionCodec) Decode(signed string) (Session, error) {
100102
if err != nil {
101103
return nil, err
102104
}
103-
if !claims.VerifyAudience(c.Audience, true) {
104-
return nil, fmt.Errorf("expected audience %q, got %q", c.Audience, claims.Audience)
105-
}
106-
if !claims.VerifyIssuer(c.Issuer, true) {
107-
return nil, fmt.Errorf("expected issuer %q, got %q", c.Issuer, claims.Issuer)
108-
}
109105
if !claims.SAMLSession {
110106
return nil, errors.New("expected saml-session")
111107
}
@@ -114,7 +110,7 @@ func (c JWTSessionCodec) Decode(signed string) (Session, error) {
114110

115111
// JWTSessionClaims represents the JWT claims in the encoded session
116112
type JWTSessionClaims struct {
117-
jwt.StandardClaims
113+
jwt.RegisteredClaims
118114
Attributes Attributes `json:"attr"`
119115
SAMLSession bool `json:"saml-session"`
120116
}

0 commit comments

Comments
 (0)