Skip to content

Commit e3ad958

Browse files
authored
chore: allow running fake idp with coderd dev (#11555)
* chore: allow running fake idp with coderd dev
1 parent c91b885 commit e3ad958

File tree

4 files changed

+108
-10
lines changed

4 files changed

+108
-10
lines changed

cmd/testidp/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# How to use
2+
3+
Start the idp service:
4+
5+
```bash
6+
$ go run main.go
7+
2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 IDP Issuer URL http://127.0.0.1:44517
8+
2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 Oauth Flags
9+
2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 --external-auth-providers='[{"type":"fake","client_id":"f2df566b-a1c9-407a-8b75-480db45c6476","client_secret":"55aca4e3-7b94-44b6-9f45-ecb5e81c560d","auth_url":"http://127.0.0.1:44517/oauth2/authorize","token_url":"http://127.0.0.1:44517/oauth2/token","validate_url":"http://127.0.0.1:44517/oauth2/userinfo","scopes":["openid","email","profile"]}]'
10+
2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 Press Ctrl+C to exit
11+
```
12+
13+
Then use the flag into your coderd instance:
14+
15+
```bash
16+
develop.sh -- --external-auth-providers='[{"type":"fake","client_id":"f2df566b-a1c9-407a-8b75-480db45c6476","client_secret":"55aca4e3-7b94-44b6-9f45-ecb5e81c560d","auth_url":"http://127.0.0.1:44517/oauth2/authorize","token_url":"http://127.0.0.1:44517/oauth2/token","validate_url":"http://127.0.0.1:44517/oauth2/userinfo","scopes":["openid","email","profile"]}]'
17+
```

cmd/testidp/main.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"log"
6+
"os"
7+
"os/signal"
8+
"testing"
9+
10+
"github.com/golang-jwt/jwt/v4"
11+
12+
"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
13+
)
14+
15+
func main() {
16+
testing.Init()
17+
_ = flag.Set("test.timeout", "0")
18+
19+
flag.Parse()
20+
21+
// This is just a way to run tests outside go test
22+
testing.Main(func(pat, str string) (bool, error) {
23+
return true, nil
24+
}, []testing.InternalTest{
25+
{
26+
Name: "Run Fake IDP",
27+
F: RunIDP(),
28+
},
29+
}, nil, nil)
30+
}
31+
32+
// RunIDP needs the testing.T because our oidctest package requires the
33+
// testing.T.
34+
func RunIDP() func(t *testing.T) {
35+
return func(t *testing.T) {
36+
idp := oidctest.NewFakeIDP(t,
37+
oidctest.WithServing(),
38+
oidctest.WithStaticUserInfo(jwt.MapClaims{}),
39+
oidctest.WithDefaultIDClaims(jwt.MapClaims{}),
40+
)
41+
id, sec := idp.AppCredentials()
42+
prov := idp.WellknownConfig()
43+
44+
log.Println("IDP Issuer URL", idp.IssuerURL())
45+
log.Println("Coderd Flags")
46+
log.Printf(`--external-auth-providers='[{"type":"fake","client_id":"%s","client_secret":"%s","auth_url":"%s","token_url":"%s","validate_url":"%s","scopes":["openid","email","profile"]}]'`,
47+
id, sec, prov.AuthURL, prov.TokenURL, prov.UserInfoURL,
48+
)
49+
50+
log.Println("Press Ctrl+C to exit")
51+
c := make(chan os.Signal, 1)
52+
signal.Notify(c, os.Interrupt)
53+
54+
// Block until ctl+c
55+
<-c
56+
log.Println("Closing")
57+
}
58+
}

coderd/coderdtest/oidctest/idp.go

+29-5
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,12 @@ type FakeIDP struct {
7878
// "Authorized Redirect URLs". This can be used to emulate that.
7979
hookValidRedirectURL func(redirectURL string) error
8080
hookUserInfo func(email string) (jwt.MapClaims, error)
81-
hookMutateToken func(token map[string]interface{})
82-
fakeCoderd func(req *http.Request) (*http.Response, error)
83-
hookOnRefresh func(email string) error
81+
// defaultIDClaims is if a new client connects and we didn't preset
82+
// some claims.
83+
defaultIDClaims jwt.MapClaims
84+
hookMutateToken func(token map[string]interface{})
85+
fakeCoderd func(req *http.Request) (*http.Response, error)
86+
hookOnRefresh func(email string) error
8487
// Custom authentication for the client. This is useful if you want
8588
// to test something like PKI auth vs a client_secret.
8689
hookAuthenticateClient func(t testing.TB, req *http.Request) (url.Values, error)
@@ -162,6 +165,12 @@ func WithStaticUserInfo(info jwt.MapClaims) func(*FakeIDP) {
162165
}
163166
}
164167

168+
func WithDefaultIDClaims(claims jwt.MapClaims) func(*FakeIDP) {
169+
return func(f *FakeIDP) {
170+
f.defaultIDClaims = claims
171+
}
172+
}
173+
165174
func WithDynamicUserInfo(userInfoFunc func(email string) (jwt.MapClaims, error)) func(*FakeIDP) {
166175
return func(f *FakeIDP) {
167176
f.hookUserInfo = userInfoFunc
@@ -679,7 +688,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
679688
// Always invalidate the code after it is used.
680689
f.codeToStateMap.Delete(code)
681690

682-
idTokenClaims, ok := f.stateToIDTokenClaims.Load(stateStr)
691+
idTokenClaims, ok := f.getClaims(f.stateToIDTokenClaims, stateStr)
683692
if !ok {
684693
t.Errorf("missing id token claims")
685694
http.Error(rw, "missing id token claims", http.StatusBadRequest)
@@ -699,7 +708,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
699708
return
700709
}
701710

702-
idTokenClaims, ok := f.refreshIDTokenClaims.Load(refreshToken)
711+
idTokenClaims, ok := f.getClaims(f.refreshIDTokenClaims, refreshToken)
703712
if !ok {
704713
t.Errorf("missing id token claims in refresh")
705714
http.Error(rw, "missing id token claims in refresh", http.StatusBadRequest)
@@ -971,6 +980,10 @@ func (f *FakeIDP) ExternalAuthConfig(t testing.TB, id string, custom *ExternalAu
971980
return cfg
972981
}
973982

983+
func (f *FakeIDP) AppCredentials() (clientID string, clientSecret string) {
984+
return f.clientID, f.clientSecret
985+
}
986+
974987
// OIDCConfig returns the OIDC config to use for Coderd.
975988
func (f *FakeIDP) OIDCConfig(t testing.TB, scopes []string, opts ...func(cfg *coderd.OIDCConfig)) *coderd.OIDCConfig {
976989
t.Helper()
@@ -1023,6 +1036,17 @@ func (f *FakeIDP) OIDCConfig(t testing.TB, scopes []string, opts ...func(cfg *co
10231036
return cfg
10241037
}
10251038

1039+
func (f *FakeIDP) getClaims(m *syncmap.Map[string, jwt.MapClaims], key string) (jwt.MapClaims, bool) {
1040+
v, ok := m.Load(key)
1041+
if !ok {
1042+
if f.defaultIDClaims != nil {
1043+
return f.defaultIDClaims, true
1044+
}
1045+
return nil, false
1046+
}
1047+
return v, true
1048+
}
1049+
10261050
func httpErrorCode(defaultCode int, err error) int {
10271051
var stautsErr statusHookError
10281052
status := defaultCode

codersdk/deployment.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -1790,11 +1790,10 @@ Write out the current server config as YAML to stdout.`,
17901790
// Env handling is done in cli.ReadGitAuthFromEnvironment
17911791
Name: "External Auth Providers",
17921792
Description: "External Authentication providers.",
1793-
// We need extra scrutiny to ensure this works, is documented, and
1794-
// tested before enabling.
1795-
YAML: "externalAuthProviders",
1796-
Value: &c.ExternalAuthConfigs,
1797-
Hidden: true,
1793+
YAML: "externalAuthProviders",
1794+
Flag: "external-auth-providers",
1795+
Value: &c.ExternalAuthConfigs,
1796+
Hidden: true,
17981797
},
17991798
{
18001799
Name: "Custom wgtunnel Host",

0 commit comments

Comments
 (0)