@@ -3,6 +3,8 @@ package coderd_test
3
3
import (
4
4
"context"
5
5
"crypto"
6
+ "crypto/rand"
7
+ "encoding/json"
6
8
"fmt"
7
9
"io"
8
10
"net/http"
@@ -13,6 +15,7 @@ import (
13
15
"time"
14
16
15
17
"github.com/coreos/go-oidc/v3/oidc"
18
+ "github.com/go-jose/go-jose/v4"
16
19
"github.com/golang-jwt/jwt/v4"
17
20
"github.com/google/go-github/v43/github"
18
21
"github.com/google/uuid"
@@ -27,10 +30,12 @@ import (
27
30
"github.com/coder/coder/v2/coderd/audit"
28
31
"github.com/coder/coder/v2/coderd/coderdtest"
29
32
"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
33
+ "github.com/coder/coder/v2/coderd/cryptokeys"
30
34
"github.com/coder/coder/v2/coderd/database"
31
35
"github.com/coder/coder/v2/coderd/database/dbauthz"
32
36
"github.com/coder/coder/v2/coderd/database/dbgen"
33
37
"github.com/coder/coder/v2/coderd/database/dbtestutil"
38
+ "github.com/coder/coder/v2/coderd/jwtutils"
34
39
"github.com/coder/coder/v2/coderd/notifications"
35
40
"github.com/coder/coder/v2/coderd/promoauth"
36
41
"github.com/coder/coder/v2/codersdk"
@@ -1316,22 +1321,25 @@ func TestUserOIDC(t *testing.T) {
1316
1321
1317
1322
owner := coderdtest .CreateFirstUser (t , client )
1318
1323
user , userData := coderdtest .CreateAnotherUser (t , client , owner .OrganizationID )
1324
+ require .Equal (t , codersdk .LoginTypePassword , userData .LoginType )
1319
1325
1320
1326
claims := jwt.MapClaims {
1321
1327
"email" : userData .Email ,
1322
1328
}
1323
1329
var err error
1324
1330
user .HTTPClient .Jar , err = cookiejar .New (nil )
1325
1331
require .NoError (t , err )
1332
+ user .HTTPClient .Transport = http .DefaultTransport .(* http.Transport ).Clone ()
1326
1333
1327
1334
ctx := testutil .Context (t , testutil .WaitShort )
1335
+
1328
1336
convertResponse , err := user .ConvertLoginType (ctx , codersdk.ConvertLoginRequest {
1329
1337
ToType : codersdk .LoginTypeOIDC ,
1330
1338
Password : "SomeSecurePassword!" ,
1331
1339
})
1332
1340
require .NoError (t , err )
1333
1341
1334
- fake .LoginWithClient (t , user , claims , func (r * http.Request ) {
1342
+ _ , _ = fake .LoginWithClient (t , user , claims , func (r * http.Request ) {
1335
1343
r .URL .RawQuery = url.Values {
1336
1344
"oidc_merge_state" : {convertResponse .StateString },
1337
1345
}.Encode ()
@@ -1341,6 +1349,99 @@ func TestUserOIDC(t *testing.T) {
1341
1349
r .AddCookie (cookie )
1342
1350
}
1343
1351
})
1352
+
1353
+ info , err := client .User (ctx , userData .ID .String ())
1354
+ require .NoError (t , err )
1355
+ require .Equal (t , codersdk .LoginTypeOIDC , info .LoginType )
1356
+ })
1357
+
1358
+ t .Run ("BadJWT" , func (t * testing.T ) {
1359
+ t .Parallel ()
1360
+
1361
+ var (
1362
+ ctx = testutil .Context (t , testutil .WaitMedium )
1363
+ logger = slogtest .Make (t , nil )
1364
+ )
1365
+
1366
+ auditor := audit .NewMock ()
1367
+ fake := oidctest .NewFakeIDP (t ,
1368
+ oidctest .WithRefresh (func (_ string ) error {
1369
+ return xerrors .New ("refreshing token should never occur" )
1370
+ }),
1371
+ oidctest .WithServing (),
1372
+ )
1373
+ cfg := fake .OIDCConfig (t , nil , func (cfg * coderd.OIDCConfig ) {
1374
+ cfg .AllowSignups = true
1375
+ })
1376
+
1377
+ db , ps := dbtestutil .NewDB (t )
1378
+ fetcher := & cryptokeys.DBFetcher {
1379
+ DB : db ,
1380
+ }
1381
+
1382
+ kc , err := cryptokeys .NewSigningCache (ctx , logger , fetcher , codersdk .CryptoKeyFeatureOIDCConvert )
1383
+ require .NoError (t , err )
1384
+
1385
+ client := coderdtest .New (t , & coderdtest.Options {
1386
+ Auditor : auditor ,
1387
+ OIDCConfig : cfg ,
1388
+ Database : db ,
1389
+ Pubsub : ps ,
1390
+ OIDCConvertKeyCache : kc ,
1391
+ })
1392
+
1393
+ owner := coderdtest .CreateFirstUser (t , client )
1394
+ user , userData := coderdtest .CreateAnotherUser (t , client , owner .OrganizationID )
1395
+
1396
+ claims := jwt.MapClaims {
1397
+ "email" : userData .Email ,
1398
+ }
1399
+ user .HTTPClient .Jar , err = cookiejar .New (nil )
1400
+ require .NoError (t , err )
1401
+ user .HTTPClient .Transport = http .DefaultTransport .(* http.Transport ).Clone ()
1402
+
1403
+ convertResponse , err := user .ConvertLoginType (ctx , codersdk.ConvertLoginRequest {
1404
+ ToType : codersdk .LoginTypeOIDC ,
1405
+ Password : "SomeSecurePassword!" ,
1406
+ })
1407
+ require .NoError (t , err )
1408
+
1409
+ // Update the cookie to use a bad signing key. We're asserting the behavior of the scenario
1410
+ // where a JWT gets minted on an old version of Coder but gets verified on a new version.
1411
+ _ , resp := fake .AttemptLogin (t , user , claims , func (r * http.Request ) {
1412
+ r .URL .RawQuery = url.Values {
1413
+ "oidc_merge_state" : {convertResponse .StateString },
1414
+ }.Encode ()
1415
+ r .Header .Set (codersdk .SessionTokenHeader , user .SessionToken ())
1416
+
1417
+ cookies := user .HTTPClient .Jar .Cookies (user .URL )
1418
+ for i , cookie := range cookies {
1419
+ if cookie .Name != coderd .OAuthConvertCookieValue {
1420
+ continue
1421
+ }
1422
+
1423
+ jwt := cookie .Value
1424
+ var claims coderd.OAuthConvertStateClaims
1425
+ err := jwtutils .Verify (ctx , kc , jwt , & claims )
1426
+ require .NoError (t , err )
1427
+ badJWT := generateBadJWT (t , claims )
1428
+ cookie .Value = badJWT
1429
+ cookies [i ] = cookie
1430
+ }
1431
+
1432
+ user .HTTPClient .Jar .SetCookies (user .URL , cookies )
1433
+
1434
+ for _ , cookie := range cookies {
1435
+ fmt .Printf ("cookie: %+v\n " , cookie )
1436
+ r .AddCookie (cookie )
1437
+ }
1438
+ })
1439
+ defer resp .Body .Close ()
1440
+ require .Equal (t , http .StatusBadRequest , resp .StatusCode )
1441
+ var respErr codersdk.Response
1442
+ err = json .NewDecoder (resp .Body ).Decode (& respErr )
1443
+ require .NoError (t , err )
1444
+ require .Contains (t , respErr .Message , "Using an invalid jwt to authorize this action." )
1344
1445
})
1345
1446
1346
1447
t .Run ("AlternateUsername" , func (t * testing.T ) {
@@ -2022,3 +2123,24 @@ func inflateClaims(t testing.TB, seed jwt.MapClaims, size int) jwt.MapClaims {
2022
2123
seed ["random_data" ] = junk
2023
2124
return seed
2024
2125
}
2126
+
2127
+ // generateBadJWT generates a JWT with a random key. It's intended to emulate the old-style JWT's we generated.
2128
+ func generateBadJWT (t * testing.T , claims interface {}) string {
2129
+ t .Helper ()
2130
+
2131
+ var buf [64 ]byte
2132
+ _ , err := rand .Read (buf [:])
2133
+ require .NoError (t , err )
2134
+ signer , err := jose .NewSigner (jose.SigningKey {
2135
+ Algorithm : jose .HS512 ,
2136
+ Key : buf [:],
2137
+ }, nil )
2138
+ require .NoError (t , err )
2139
+ payload , err := json .Marshal (claims )
2140
+ require .NoError (t , err )
2141
+ signed , err := signer .Sign (payload )
2142
+ require .NoError (t , err )
2143
+ compact , err := signed .CompactSerialize ()
2144
+ require .NoError (t , err )
2145
+ return compact
2146
+ }
0 commit comments