Skip to content

fix: make 'NoRefresh' honor unlimited tokens in gitauth #9472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor rest of gitauth tests
  • Loading branch information
Emyrk committed Aug 31, 2023
commit 4637ddb714f2f5495ad594154e9f801d007995e8
53 changes: 44 additions & 9 deletions coderd/coderdtest/oidctest/idp.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"net"
Expand Down Expand Up @@ -66,7 +67,7 @@ type FakeIDP struct {
// IDP -> Application. Almost all IDPs have the concept of
// "Authorized Redirect URLs". This can be used to emulate that.
hookValidRedirectURL func(redirectURL string) error
hookUserInfo func(email string) jwt.MapClaims
hookUserInfo func(email string) (jwt.MapClaims, error)
fakeCoderd func(req *http.Request) (*http.Response, error)
hookOnRefresh func(email string) error
// Custom authentication for the client. This is useful if you want
Expand All @@ -75,6 +76,26 @@ type FakeIDP struct {
serve bool
}

func StatusError(code int, err error) error {
return statusHookError{
Err: err,
HTTPStatusCode: code,
}
}

// statusHookError allows a hook to change the returned http status code.
type statusHookError struct {
Err error
HTTPStatusCode int
}

func (s statusHookError) Error() string {
if s.Err == nil {
return ""
}
return s.Err.Error()
}

type FakeIDPOpt func(idp *FakeIDP)

func WithAuthorizedRedirectURL(hook func(redirectURL string) error) func(*FakeIDP) {
Expand Down Expand Up @@ -108,13 +129,13 @@ func WithLogging(t testing.TB, options *slogtest.Options) func(*FakeIDP) {
// every user on the /userinfo endpoint.
func WithStaticUserInfo(info jwt.MapClaims) func(*FakeIDP) {
return func(f *FakeIDP) {
f.hookUserInfo = func(_ string) jwt.MapClaims {
return info
f.hookUserInfo = func(_ string) (jwt.MapClaims, error) {
return info, nil
}
}
}

func WithDynamicUserInfo(userInfoFunc func(email string) jwt.MapClaims) func(*FakeIDP) {
func WithDynamicUserInfo(userInfoFunc func(email string) (jwt.MapClaims, error)) func(*FakeIDP) {
return func(f *FakeIDP) {
f.hookUserInfo = userInfoFunc
}
Expand Down Expand Up @@ -160,7 +181,7 @@ func NewFakeIDP(t testing.TB, opts ...FakeIDPOpt) *FakeIDP {
stateToIDTokenClaims: syncmap.New[string, jwt.MapClaims](),
refreshIDTokenClaims: syncmap.New[string, jwt.MapClaims](),
hookOnRefresh: func(_ string) error { return nil },
hookUserInfo: func(email string) jwt.MapClaims { return jwt.MapClaims{} },
hookUserInfo: func(email string) (jwt.MapClaims, error) { return jwt.MapClaims{}, nil },
hookValidRedirectURL: func(redirectURL string) error { return nil },
}

Expand Down Expand Up @@ -489,7 +510,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
err := f.hookValidRedirectURL(redirectURI)
if err != nil {
t.Errorf("not authorized redirect_uri by custom hook %q: %s", redirectURI, err.Error())
http.Error(rw, fmt.Sprintf("invalid redirect_uri: %s", err.Error()), http.StatusBadRequest)
http.Error(rw, fmt.Sprintf("invalid redirect_uri: %s", err.Error()), httpErrorCode(http.StatusBadRequest, err))
return
}

Expand All @@ -515,7 +536,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
slog.F("values", values.Encode()),
)
if err != nil {
http.Error(rw, fmt.Sprintf("invalid token request: %s", err.Error()), http.StatusBadRequest)
http.Error(rw, fmt.Sprintf("invalid token request: %s", err.Error()), httpErrorCode(http.StatusBadRequest, err))
return
}
getEmail := func(claims jwt.MapClaims) string {
Expand Down Expand Up @@ -576,7 +597,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
claims = idTokenClaims
err := f.hookOnRefresh(getEmail(claims))
if err != nil {
http.Error(rw, fmt.Sprintf("refresh hook blocked refresh: %s", err.Error()), http.StatusBadRequest)
http.Error(rw, fmt.Sprintf("refresh hook blocked refresh: %s", err.Error()), httpErrorCode(http.StatusBadRequest, err))
return
}

Expand Down Expand Up @@ -624,7 +645,12 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
http.Error(rw, "invalid access token, missing user info", http.StatusBadRequest)
return
}
_ = json.NewEncoder(rw).Encode(f.hookUserInfo(email))
claims, err := f.hookUserInfo(email)
if err != nil {
http.Error(rw, fmt.Sprintf("user info hook returned error: %s", err.Error()), httpErrorCode(http.StatusBadRequest, err))
return
}
_ = json.NewEncoder(rw).Encode(claims)
}))

mux.Handle(keysPath, http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -782,6 +808,15 @@ func (f *FakeIDP) OIDCConfig(t testing.TB, scopes []string, opts ...func(cfg *co
return cfg
}

func httpErrorCode(defaultCode int, err error) int {
var stautsErr statusHookError
var status = defaultCode
if errors.As(err, &stautsErr) {
status = stautsErr.HTTPStatusCode
}
return status
}

type fakeRoundTripper struct {
roundTrip func(req *http.Request) (*http.Response, error)
}
Expand Down
2 changes: 1 addition & 1 deletion coderd/gitauth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type Config struct {
}

// RefreshToken automatically refreshes the token if expired and permitted.
// It returns the token and a bool indicating if the token was refreshed.
// It returns the token and a bool indicating if the token is valid.
func (c *Config) RefreshToken(ctx context.Context, db database.Store, gitAuthLink database.GitAuthLink) (database.GitAuthLink, bool, error) {
// If the token is expired and refresh is disabled, we prompt
// the user to authenticate again.
Expand Down
Loading