mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: allow for alternate usernames on conflict (#4614)
This commit is contained in:
+55
-47
@@ -3,13 +3,12 @@ package coderd_test
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/golang-jwt/jwt"
|
||||
@@ -450,17 +449,19 @@ func TestUserOIDC(t *testing.T) {
|
||||
tc := tc
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := createOIDCConfig(t, tc.Claims)
|
||||
conf := coderdtest.NewOIDCConfig(t, "")
|
||||
|
||||
config := conf.OIDCConfig()
|
||||
config.AllowSignups = tc.AllowSignups
|
||||
config.EmailDomain = tc.EmailDomain
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
OIDCConfig: config,
|
||||
})
|
||||
resp := oidcCallback(t, client)
|
||||
resp := oidcCallback(t, client, conf.EncodeClaims(t, tc.Claims))
|
||||
assert.Equal(t, tc.StatusCode, resp.StatusCode)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
ctx, _ := testutil.Context(t)
|
||||
|
||||
if tc.Username != "" {
|
||||
client.SessionToken = authCookieValue(resp.Cookies())
|
||||
@@ -478,10 +479,50 @@ func TestUserOIDC(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("AlternateUsername", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := coderdtest.NewOIDCConfig(t, "")
|
||||
|
||||
config := conf.OIDCConfig()
|
||||
config.AllowSignups = true
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
OIDCConfig: config,
|
||||
})
|
||||
|
||||
code := conf.EncodeClaims(t, jwt.MapClaims{
|
||||
"email": "jon@coder.com",
|
||||
})
|
||||
resp := oidcCallback(t, client, code)
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
|
||||
ctx, _ := testutil.Context(t)
|
||||
|
||||
client.SessionToken = authCookieValue(resp.Cookies())
|
||||
user, err := client.User(ctx, "me")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "jon", user.Username)
|
||||
|
||||
// Pass a different subject field so that we prompt creating a
|
||||
// new user.
|
||||
code = conf.EncodeClaims(t, jwt.MapClaims{
|
||||
"email": "jon@example2.com",
|
||||
"sub": "diff",
|
||||
})
|
||||
resp = oidcCallback(t, client, code)
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
|
||||
client.SessionToken = authCookieValue(resp.Cookies())
|
||||
user, err = client.User(ctx, "me")
|
||||
require.NoError(t, err)
|
||||
require.True(t, strings.HasPrefix(user.Username, "jon-"), "username %q should have prefix %q", user.Username, "jon-")
|
||||
})
|
||||
|
||||
t.Run("Disabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
resp := oidcCallback(t, client)
|
||||
resp := oidcCallback(t, client, "asdf")
|
||||
require.Equal(t, http.StatusPreconditionRequired, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -492,7 +533,7 @@ func TestUserOIDC(t *testing.T) {
|
||||
OAuth2Config: &oauth2Config{},
|
||||
},
|
||||
})
|
||||
resp := oidcCallback(t, client)
|
||||
resp := oidcCallback(t, client, "asdf")
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -514,48 +555,16 @@ func TestUserOIDC(t *testing.T) {
|
||||
Verifier: verifier,
|
||||
},
|
||||
})
|
||||
resp := oidcCallback(t, client)
|
||||
resp := oidcCallback(t, client, "asdf")
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
// createOIDCConfig generates a new OIDCConfig that returns a static token
|
||||
// with the claims provided.
|
||||
func createOIDCConfig(t *testing.T, claims jwt.MapClaims) *coderd.OIDCConfig {
|
||||
t.Helper()
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
require.NoError(t, err)
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
|
||||
claims["exp"] = time.Now().Add(time.Hour).UnixMilli()
|
||||
claims["iss"] = "https://coder.com"
|
||||
claims["sub"] = "hello"
|
||||
|
||||
signed, err := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifier := oidc.NewVerifier("https://coder.com", &oidc.StaticKeySet{
|
||||
PublicKeys: []crypto.PublicKey{key.Public()},
|
||||
}, &oidc.Config{
|
||||
SkipClientIDCheck: true,
|
||||
})
|
||||
|
||||
return &coderd.OIDCConfig{
|
||||
OAuth2Config: &oauth2Config{
|
||||
token: (&oauth2.Token{
|
||||
AccessToken: "token",
|
||||
}).WithExtra(map[string]interface{}{
|
||||
"id_token": signed,
|
||||
}),
|
||||
},
|
||||
Verifier: verifier,
|
||||
}
|
||||
}
|
||||
|
||||
func oauth2Callback(t *testing.T, client *codersdk.Client) *http.Response {
|
||||
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
state := "somestate"
|
||||
oauthURL, err := client.URL.Parse("/api/v2/users/oauth2/github/callback?code=asd&state=" + state)
|
||||
require.NoError(t, err)
|
||||
@@ -573,19 +582,18 @@ func oauth2Callback(t *testing.T, client *codersdk.Client) *http.Response {
|
||||
return res
|
||||
}
|
||||
|
||||
func oidcCallback(t *testing.T, client *codersdk.Client) *http.Response {
|
||||
func oidcCallback(t *testing.T, client *codersdk.Client, code string) *http.Response {
|
||||
t.Helper()
|
||||
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
state := "somestate"
|
||||
oauthURL, err := client.URL.Parse("/api/v2/users/oidc/callback?code=asd&state=" + state)
|
||||
oauthURL, err := client.URL.Parse(fmt.Sprintf("/api/v2/users/oidc/callback?code=%s&state=somestate", code))
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequestWithContext(context.Background(), "GET", oauthURL.String(), nil)
|
||||
require.NoError(t, err)
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: codersdk.OAuth2StateKey,
|
||||
Value: state,
|
||||
Value: "somestate",
|
||||
})
|
||||
res, err := client.HTTPClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user