@@ -39,6 +39,12 @@ import (
39
39
"github.com/coder/coder/v2/codersdk"
40
40
)
41
41
42
+ type token struct {
43
+ issued time.Time
44
+ email string
45
+ exp time.Time
46
+ }
47
+
42
48
// FakeIDP is a functional OIDC provider.
43
49
// It only supports 1 OIDC client.
44
50
type FakeIDP struct {
@@ -65,7 +71,7 @@ type FakeIDP struct {
65
71
// That is the various access tokens, refresh tokens, states, etc.
66
72
codeToStateMap * syncmap.Map [string ,string ]
67
73
// Token -> Email
68
- accessTokens * syncmap.Map [string ,string ]
74
+ accessTokens * syncmap.Map [string ,token ]
69
75
// Refresh Token -> Email
70
76
refreshTokensUsed * syncmap.Map [string ,bool ]
71
77
refreshTokens * syncmap.Map [string ,string ]
@@ -173,6 +179,12 @@ func WithLogging(t testing.TB, options *slogtest.Options) func(*FakeIDP) {
173
179
}
174
180
}
175
181
182
+ func WithLogger (logger slog.Logger )func (* FakeIDP ) {
183
+ return func (f * FakeIDP ) {
184
+ f .logger = logger
185
+ }
186
+ }
187
+
176
188
// WithStaticUserInfo is optional, but will return the same user info for
177
189
// every user on the /userinfo endpoint.
178
190
func WithStaticUserInfo (info jwt.MapClaims )func (* FakeIDP ) {
@@ -229,7 +241,7 @@ func NewFakeIDP(t testing.TB, opts ...FakeIDPOpt) *FakeIDP {
229
241
clientSecret :uuid .NewString (),
230
242
logger :slog .Make (),
231
243
codeToStateMap :syncmap .New [string ,string ](),
232
- accessTokens :syncmap .New [string ,string ](),
244
+ accessTokens :syncmap .New [string ,token ](),
233
245
refreshTokens :syncmap .New [string ,string ](),
234
246
refreshTokensUsed :syncmap .New [string ,bool ](),
235
247
stateToIDTokenClaims :syncmap .New [string , jwt.MapClaims ](),
@@ -284,7 +296,7 @@ func (f *FakeIDP) updateIssuerURL(t testing.TB, issuer string) {
284
296
Algorithms : []string {
285
297
"RS256" ,
286
298
},
287
- ExternalAuthURL :u .ResolveReference (& url.URL {Path :fmt . Sprintf ( "/external-auth-validate/%s" , f . externalProviderID ) }).String (),
299
+ ExternalAuthURL :u .ResolveReference (& url.URL {Path :"/external-auth-validate/user" }).String (),
288
300
}
289
301
}
290
302
@@ -417,7 +429,7 @@ func (f *FakeIDP) LoginWithClient(t testing.TB, client *codersdk.Client, idToken
417
429
// ExternalLogin does the oauth2 flow for external auth providers. This requires
418
430
// an authenticated coder client.
419
431
func (f * FakeIDP )ExternalLogin (t testing.TB ,client * codersdk.Client ,opts ... func (r * http.Request )) {
420
- coderOauthURL ,err := client .URL .Parse (fmt . Sprintf ( "/external-auth/%s/ callback" , f . externalProviderID ) )
432
+ coderOauthURL ,err := client .URL .Parse ("/external-auth/callback" )
421
433
require .NoError (t ,err )
422
434
f .SetRedirect (t ,coderOauthURL .String ())
423
435
@@ -544,9 +556,13 @@ func (f *FakeIDP) newCode(state string) string {
544
556
545
557
// newToken enforces the access token exchanged is actually a valid access token
546
558
// created by the IDP.
547
- func (f * FakeIDP )newToken (email string )string {
559
+ func (f * FakeIDP )newToken (email string , expires time. Time )string {
548
560
accessToken := uuid .NewString ()
549
- f .accessTokens .Store (accessToken ,email )
561
+ f .accessTokens .Store (accessToken ,token {
562
+ issued :time .Now (),
563
+ email :email ,
564
+ exp :expires ,
565
+ })
550
566
return accessToken
551
567
}
552
568
@@ -562,10 +578,15 @@ func (f *FakeIDP) authenticateBearerTokenRequest(t testing.TB, req *http.Request
562
578
563
579
auth := req .Header .Get ("Authorization" )
564
580
token := strings .TrimPrefix (auth ,"Bearer " )
565
- _ ,ok := f .accessTokens .Load (token )
581
+ authToken ,ok := f .accessTokens .Load (token )
566
582
if ! ok {
567
583
return "" ,xerrors .New ("invalid access token" )
568
584
}
585
+
586
+ if ! authToken .exp .IsZero ()&& authToken .exp .Before (time .Now ()) {
587
+ return "" ,xerrors .New ("access token expired" )
588
+ }
589
+
569
590
return token ,nil
570
591
}
571
592
@@ -690,7 +711,8 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
690
711
mux .Handle (tokenPath ,http .HandlerFunc (func (rw http.ResponseWriter ,r * http.Request ) {
691
712
values ,err := f .authenticateOIDCClientRequest (t ,r )
692
713
f .logger .Info (r .Context (),"http idp call token" ,
693
- slog .Error (err ),
714
+ slog .F ("valid" ,err == nil ),
715
+ slog .F ("grant_type" ,values .Get ("grant_type" )),
694
716
slog .F ("values" ,values .Encode ()),
695
717
)
696
718
if err != nil {
@@ -773,7 +795,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
773
795
email := getEmail (claims )
774
796
refreshToken := f .newRefreshTokens (email )
775
797
token := map [string ]interface {}{
776
- "access_token" :f .newToken (email ),
798
+ "access_token" :f .newToken (email , exp ),
777
799
"refresh_token" :refreshToken ,
778
800
"token_type" :"Bearer" ,
779
801
"expires_in" :int64 ((f .defaultExpire ).Seconds ()),
@@ -791,25 +813,31 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
791
813
792
814
validateMW := func (rw http.ResponseWriter ,r * http.Request ) (email string ,ok bool ) {
793
815
token ,err := f .authenticateBearerTokenRequest (t ,r )
794
- f .logger .Info (r .Context (),"http call idp user info" ,
795
- slog .Error (err ),
796
- slog .F ("url" ,r .URL .String ()),
797
- )
798
816
if err != nil {
799
- http .Error (rw ,fmt .Sprintf ("invalid user info request: %s" ,err .Error ()),http .StatusBadRequest )
817
+ http .Error (rw ,fmt .Sprintf ("invalid user info request: %s" ,err .Error ()),http .StatusUnauthorized )
800
818
return "" ,false
801
819
}
802
820
803
- email ,ok = f .accessTokens .Load (token )
821
+ authToken ,ok : =f .accessTokens .Load (token )
804
822
if ! ok {
805
823
t .Errorf ("access token user for user_info has no email to indicate which user" )
806
- http .Error (rw ,"invalid access token, missing user info" ,http .StatusBadRequest )
824
+ http .Error (rw ,"invalid access token, missing user info" ,http .StatusUnauthorized )
825
+ return "" ,false
826
+ }
827
+
828
+ if ! authToken .exp .IsZero ()&& authToken .exp .Before (time .Now ()) {
829
+ http .Error (rw ,"auth token expired" ,http .StatusUnauthorized )
807
830
return "" ,false
808
831
}
809
- return email ,true
832
+
833
+ return authToken .email ,true
810
834
}
811
835
mux .Handle (userInfoPath ,http .HandlerFunc (func (rw http.ResponseWriter ,r * http.Request ) {
812
836
email ,ok := validateMW (rw ,r )
837
+ f .logger .Info (r .Context (),"http userinfo" ,
838
+ slog .F ("valid" ,ok ),
839
+ slog .F ("email" ,email ),
840
+ )
813
841
if ! ok {
814
842
return
815
843
}
@@ -827,6 +855,10 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
827
855
// should be strict, and this one needs to handle sub routes.
828
856
mux .Mount ("/external-auth-validate/" ,http .HandlerFunc (func (rw http.ResponseWriter ,r * http.Request ) {
829
857
email ,ok := validateMW (rw ,r )
858
+ f .logger .Info (r .Context (),"http external auth validate" ,
859
+ slog .F ("valid" ,ok ),
860
+ slog .F ("email" ,email ),
861
+ )
830
862
if ! ok {
831
863
return
832
864
}
@@ -978,7 +1010,7 @@ func (f *FakeIDP) ExternalAuthConfig(t testing.TB, id string, custom *ExternalAu
978
1010
}
979
1011
f .externalProviderID = id
980
1012
f .externalAuthValidate = func (email string ,rw http.ResponseWriter ,r * http.Request ) {
981
- newPath := strings .TrimPrefix (r .URL .Path ,fmt . Sprintf ( "/external-auth-validate/%s" , id ) )
1013
+ newPath := strings .TrimPrefix (r .URL .Path ,"/external-auth-validate" )
982
1014
switch newPath {
983
1015
// /user is ALWAYS supported under the `/` path too.
984
1016
case "/user" ,"/" ,"" :
@@ -1010,7 +1042,7 @@ func (f *FakeIDP) ExternalAuthConfig(t testing.TB, id string, custom *ExternalAu
1010
1042
DisplayIcon :f .WellknownConfig ().UserInfoURL ,
1011
1043
// Omit the /user for the validate so we can easily append to it when modifying
1012
1044
// the cfg for advanced tests.
1013
- ValidateURL :f .issuerURL .ResolveReference (& url.URL {Path :fmt . Sprintf ( "/external-auth-validate/%s" , id ) }).String (),
1045
+ ValidateURL :f .issuerURL .ResolveReference (& url.URL {Path :"/external-auth-validate/user" }).String (),
1014
1046
}
1015
1047
for _ ,opt := range opts {
1016
1048
opt (cfg )