// TestClientSample makes sure that the clients.json.sample file is valid and can be loaded properly. func TestClientSample(t *testing.T) { f, err := os.Open(clientsFile) if err != nil { t.Fatalf("could not open file %q: %v", clientsFile, err) } defer f.Close() clients, err := client.ClientsFromReader(f) if err != nil { t.Fatalf("Error loading Clients: %v", err) } memDB := db.NewMemDB() repo, err := db.NewClientRepoFromClients(memDB, clients) if err != nil { t.Fatalf("Error creating Clients: %v", err) } mgr := manager.NewClientManager(repo, db.TransactionFactory(memDB), manager.ManagerOptions{}) for i, c := range clients { ok, err := mgr.Authenticate(c.Client.Credentials) if !ok { t.Errorf("case %d: couldn't authenticate", i) } if err != nil { t.Errorf("case %d: error authenticating: %v", i, err) } } }
func makeAdminAPITestFixtures() *adminAPITestFixtures { f := &adminAPITestFixtures{} dbMap, ur, pwr, um := makeUserObjects(adminUsers, adminPasswords) var cliCount int secGen := func() ([]byte, error) { id := []byte(fmt.Sprintf("client_%v", cliCount)) cliCount++ return id, nil } cr := db.NewClientRepo(dbMap) clientIDGenerator := func(hostport string) (string, error) { return fmt.Sprintf("client_%v", hostport), nil } cm := manager.NewClientManager(cr, db.TransactionFactory(dbMap), manager.ManagerOptions{SecretGenerator: secGen, ClientIDGenerator: clientIDGenerator}) ccr := db.NewConnectorConfigRepo(dbMap) f.cr = cr f.ur = ur f.pwr = pwr f.adAPI = admin.NewAdminAPI(ur, pwr, cr, ccr, um, cm, "local") f.adSrv = server.NewAdminServer(f.adAPI, nil, adminAPITestSecret) f.hSrv = httptest.NewServer(f.adSrv.HTTPHandler()) f.hc = &http.Client{ Transport: &adminAPITransport{ secret: adminAPITestSecret, }, } f.adClient, _ = adminschema.NewWithBasePath(f.hc, f.hSrv.URL) return f }
func TestDBClientRepoAuthenticate(t *testing.T) { c := connect(t) r := db.NewClientRepo(c) clientIDGenerator := func(hostport string) (string, error) { return hostport, nil } secGen := func() ([]byte, error) { return []byte("secret"), nil } m := manager.NewClientManager(r, db.TransactionFactory(c), manager.ManagerOptions{ClientIDGenerator: clientIDGenerator, SecretGenerator: secGen}) cm := oidc.ClientMetadata{ RedirectURIs: []url.URL{ url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"}, }, } cli := client.Client{ Metadata: cm, } cc, err := m.New(cli, nil) if err != nil { t.Fatalf(err.Error()) } if cc.ID != "127.0.0.1:5556" { t.Fatalf("Returned ClientCredentials has incorrect ID: want=baz got=%s", cc.ID) } ok, err := m.Authenticate(*cc) if err != nil { t.Fatalf("Unexpected error: %v", err) } else if !ok { t.Fatalf("Authentication failed for good creds") } creds := []oidc.ClientCredentials{ // completely made up oidc.ClientCredentials{ID: "foo", Secret: "bar"}, // good client ID, bad secret oidc.ClientCredentials{ID: cc.ID, Secret: "bar"}, // bad client ID, good secret oidc.ClientCredentials{ID: "foo", Secret: cc.Secret}, // good client ID, secret with some fluff on the end oidc.ClientCredentials{ID: cc.ID, Secret: fmt.Sprintf("%sfluff", cc.Secret)}, } for i, c := range creds { ok, err := m.Authenticate(c) if err != nil { t.Errorf("case %d: unexpected error: %v", i, err) } else if ok { t.Errorf("case %d: authentication succeeded for bad creds", i) } } }
func makeTestFixtures() *testFixtures { f := &testFixtures{} dbMap := db.NewMemDB() f.ur = func() user.UserRepo { repo, err := db.NewUserRepoFromUsers(dbMap, []user.UserWithRemoteIdentities{ { User: user.User{ ID: "ID-1", Email: "*****@*****.**", DisplayName: "Name-1", }, }, { User: user.User{ ID: "ID-2", Email: "*****@*****.**", DisplayName: "Name-2", }, }, }) if err != nil { panic("Failed to create user repo: " + err.Error()) } return repo }() f.pwr = func() user.PasswordInfoRepo { repo, err := db.NewPasswordInfoRepoFromPasswordInfos(dbMap, []user.PasswordInfo{ { UserID: "ID-1", Password: []byte("hi."), }, }) if err != nil { panic("Failed to create user repo: " + err.Error()) } return repo }() f.ccr = func() connector.ConnectorConfigRepo { c := []connector.ConnectorConfig{&connector.LocalConnectorConfig{ID: "local"}} repo := db.NewConnectorConfigRepo(dbMap) if err := repo.Set(c); err != nil { panic(err) } return repo }() f.mgr = manager.NewUserManager(f.ur, f.pwr, f.ccr, db.TransactionFactory(dbMap), manager.ManagerOptions{}) f.cm = clientmanager.NewClientManager(f.cr, db.TransactionFactory(dbMap), clientmanager.ManagerOptions{}) f.adAPI = NewAdminAPI(f.ur, f.pwr, f.cr, f.ccr, f.mgr, f.cm, "local") return f }
func TestCreate(t *testing.T) { dbm := db.NewMemDB() repo := db.NewClientRepo(dbm) manager := manager.NewClientManager(repo, db.TransactionFactory(dbm), manager.ManagerOptions{}) res := &clientResource{manager: manager} tests := [][]string{ []string{"http://example.com"}, []string{"https://example.com"}, []string{"http://example.com/foo"}, []string{"http://example.com/bar", "http://example.com/foo"}, } endpoint := "http://example.com/clients" for i, tt := range tests { body := strings.NewReader(fmt.Sprintf(`{"redirectURIs":["%s"]}`, strings.Join(tt, `","`))) r, err := http.NewRequest("POST", endpoint, body) if err != nil { t.Fatalf("Failed creating http.Request: %v", err) } r.Header.Set("content-type", "application/json") w := httptest.NewRecorder() res.ServeHTTP(w, r) if w.Code != http.StatusCreated { t.Errorf("case %d: invalid response code, want=%d, got=%d", i, http.StatusCreated, w.Code) } var client schema.ClientWithSecret if err := json.Unmarshal(w.Body.Bytes(), &client); err != nil { t.Errorf("case %d: unexpected error=%v", i, err) } if len(client.RedirectURIs) != len(tt) { t.Errorf("case %d: unexpected number of redirect URIs, want=%d, got=%d", i, len(tt), len(client.RedirectURIs)) } if !reflect.DeepEqual(tt, client.RedirectURIs) { t.Errorf("case %d: unexpected client redirect URIs: want=%v got=%v", i, tt, client.RedirectURIs) } if client.Id == "" { t.Errorf("case %d: empty client ID in response", i) } if client.Secret == "" { t.Errorf("case %d: empty client secret in response", i) } wantLoc := fmt.Sprintf("%s/%s", endpoint, client.Id) gotLoc := w.Header().Get("Location") if gotLoc != wantLoc { t.Errorf("case %d: invalid location header, want=%v, got=%v", i, wantLoc, gotLoc) } } }
func TestDBClientRepoMetadataNoExist(t *testing.T) { c := connect(t) r := db.NewClientRepo(c) m := manager.NewClientManager(r, db.TransactionFactory(c), manager.ManagerOptions{}) got, err := m.Metadata("noexist") if err != client.ErrorNotFound { t.Errorf("want==%q, got==%q", client.ErrorNotFound, err) } if got != nil { t.Fatalf("Retrieved incorrect ClientMetadata: want=nil got=%#v", got) } }
func newDBConnector(dsn string) (*dbConnector, error) { dbc, err := db.NewConnection(db.Config{DSN: dsn}) if err != nil { return nil, err } dConn := &dbConnector{ cfgRepo: db.NewConnectorConfigRepo(dbc), ciManager: manager.NewClientManager(db.NewClientRepo(dbc), db.TransactionFactory(dbc), manager.ManagerOptions{}), } return dConn, nil }
func makeClientRepoAndManager(dbMap *gorp.DbMap, clients []client.LoadableClient) (client.ClientRepo, *clientmanager.ClientManager, error) { clientIDGenerator := func(hostport string) (string, error) { return hostport, nil } secGen := func() ([]byte, error) { return []byte("secret"), nil } clientRepo, err := db.NewClientRepoFromClients(dbMap, clients) if err != nil { return nil, nil, err } clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbMap), clientmanager.ManagerOptions{ClientIDGenerator: clientIDGenerator, SecretGenerator: secGen}) return clientRepo, clientManager, nil }
func (cfg *MultiServerConfig) Configure(srv *Server) error { if len(cfg.KeySecrets) == 0 { return errors.New("missing key secret") } if cfg.DatabaseConfig.DSN == "" { return errors.New("missing database connection string") } dbc, err := db.NewConnection(cfg.DatabaseConfig) if err != nil { return fmt.Errorf("unable to initialize database connection: %v", err) } if _, ok := dbc.Dialect.(gorp.PostgresDialect); !ok { return errors.New("only postgres backend supported for multi server configurations") } kRepo, err := db.NewPrivateKeySetRepo(dbc, cfg.UseOldFormat, cfg.KeySecrets...) if err != nil { return fmt.Errorf("unable to create PrivateKeySetRepo: %v", err) } ciRepo := db.NewClientRepo(dbc) sRepo := db.NewSessionRepo(dbc) skRepo := db.NewSessionKeyRepo(dbc) cfgRepo := db.NewConnectorConfigRepo(dbc) userRepo := db.NewUserRepo(dbc) pwiRepo := db.NewPasswordInfoRepo(dbc) userManager := usermanager.NewUserManager(userRepo, pwiRepo, cfgRepo, db.TransactionFactory(dbc), usermanager.ManagerOptions{}) clientManager := clientmanager.NewClientManager(ciRepo, db.TransactionFactory(dbc), clientmanager.ManagerOptions{}) refreshTokenRepo := db.NewRefreshTokenRepo(dbc) sm := sessionmanager.NewSessionManager(sRepo, skRepo) srv.ClientRepo = ciRepo srv.ClientManager = clientManager srv.KeySetRepo = kRepo srv.ConnectorConfigRepo = cfgRepo srv.UserRepo = userRepo srv.UserManager = userManager srv.PasswordInfoRepo = pwiRepo srv.SessionManager = sm srv.RefreshTokenRepo = refreshTokenRepo srv.HealthChecks = append(srv.HealthChecks, db.NewHealthChecker(dbc)) srv.dbMap = dbc return nil }
func (cfg *SingleServerConfig) Configure(srv *Server) error { k, err := key.GeneratePrivateKey() if err != nil { return err } dbMap := db.NewMemDB() ks := key.NewPrivateKeySet([]*key.PrivateKey{k}, time.Now().Add(24*time.Hour)) kRepo := key.NewPrivateKeySetRepo() if err = kRepo.Set(ks); err != nil { return err } clients, err := loadClients(cfg.ClientsFile) if err != nil { return fmt.Errorf("unable to read clients from file %s: %v", cfg.ClientsFile, err) } clientRepo, err := db.NewClientRepoFromClients(dbMap, clients) if err != nil { return err } f, err := os.Open(cfg.ConnectorsFile) if err != nil { return fmt.Errorf("opening connectors file: %v", err) } defer f.Close() cfgs, err := connector.ReadConfigs(f) if err != nil { return fmt.Errorf("decoding connector configs: %v", err) } cfgRepo := db.NewConnectorConfigRepo(dbMap) if err := cfgRepo.Set(cfgs); err != nil { return fmt.Errorf("failed to set connectors: %v", err) } sRepo := db.NewSessionRepo(dbMap) skRepo := db.NewSessionKeyRepo(dbMap) sm := sessionmanager.NewSessionManager(sRepo, skRepo) users, pwis, err := loadUsers(cfg.UsersFile) if err != nil { return fmt.Errorf("unable to read users from file: %v", err) } userRepo, err := db.NewUserRepoFromUsers(dbMap, users) if err != nil { return err } pwiRepo, err := db.NewPasswordInfoRepoFromPasswordInfos(dbMap, pwis) if err != nil { return err } refTokRepo := db.NewRefreshTokenRepo(dbMap) txnFactory := db.TransactionFactory(dbMap) userManager := usermanager.NewUserManager(userRepo, pwiRepo, cfgRepo, txnFactory, usermanager.ManagerOptions{}) clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbMap), clientmanager.ManagerOptions{}) if err != nil { return fmt.Errorf("Failed to create client identity manager: %v", err) } srv.ClientRepo = clientRepo srv.ClientManager = clientManager srv.KeySetRepo = kRepo srv.ConnectorConfigRepo = cfgRepo srv.UserRepo = userRepo srv.UserManager = userManager srv.PasswordInfoRepo = pwiRepo srv.SessionManager = sm srv.RefreshTokenRepo = refTokRepo srv.HealthChecks = append(srv.HealthChecks, db.NewHealthChecker(dbMap)) srv.dbMap = dbMap return nil }
func makeTestFixtures(clientCredsFlag bool) (*UsersAPI, *testEmailer) { dbMap := db.NewMemDB() ur := func() user.UserRepo { repo, err := db.NewUserRepoFromUsers(dbMap, []user.UserWithRemoteIdentities{ { User: user.User{ ID: "ID-1", Email: "*****@*****.**", Admin: true, CreatedAt: clock.Now(), }, }, { User: user.User{ ID: "ID-2", Email: "*****@*****.**", EmailVerified: true, CreatedAt: clock.Now(), }, }, { User: user.User{ ID: "ID-3", Email: "*****@*****.**", CreatedAt: clock.Now(), }, }, { User: user.User{ ID: "ID-4", Email: "*****@*****.**", CreatedAt: clock.Now(), Disabled: true, }, }, }) if err != nil { panic("Failed to create user repo: " + err.Error()) } return repo }() pwr := func() user.PasswordInfoRepo { repo, err := db.NewPasswordInfoRepoFromPasswordInfos(dbMap, []user.PasswordInfo{ { UserID: "ID-1", Password: []byte("password-1"), }, { UserID: "ID-2", Password: []byte("password-2"), }, }) if err != nil { panic("Failed to create user repo: " + err.Error()) } return repo }() ccr := func() connector.ConnectorConfigRepo { repo := db.NewConnectorConfigRepo(dbMap) c := []connector.ConnectorConfig{ &connector.LocalConnectorConfig{ID: "local"}, } if err := repo.Set(c); err != nil { panic(err) } return repo }() mgr := manager.NewUserManager(ur, pwr, ccr, db.TransactionFactory(dbMap), manager.ManagerOptions{}) mgr.Clock = clock ci := client.Client{ Credentials: oidc.ClientCredentials{ ID: goodClientID, Secret: base64.URLEncoding.EncodeToString([]byte("secret")), }, Metadata: oidc.ClientMetadata{ RedirectURIs: []url.URL{ validRedirURL, }, }, } ci2 := client.Client{ Credentials: oidc.ClientCredentials{ ID: nonAdminClientID, Secret: base64.URLEncoding.EncodeToString([]byte("anothersecret")), }, Metadata: oidc.ClientMetadata{ RedirectURIs: []url.URL{ validRedirURL2, }, }, } clientIDGenerator := func(hostport string) (string, error) { return hostport, nil } secGen := func() ([]byte, error) { return []byte("secret"), nil } clientRepo, err := db.NewClientRepoFromClients(dbMap, []client.LoadableClient{{Client: ci}, {Client: ci2}}) if err != nil { panic("Failed to create client manager: " + err.Error()) } clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbMap), clientmanager.ManagerOptions{ClientIDGenerator: clientIDGenerator, SecretGenerator: secGen}) // Used in TestRevokeRefreshToken test. refreshTokens := []struct { clientID string userID string }{ {goodClientID, "ID-1"}, {goodClientID, "ID-2"}, } refreshRepo := db.NewRefreshTokenRepo(dbMap) for _, token := range refreshTokens { if _, err := refreshRepo.Create(token.userID, token.clientID, "local", []string{"openid"}); err != nil { panic("Failed to create refresh token: " + err.Error()) } } emailer := &testEmailer{} api := NewUsersAPI(mgr, clientManager, refreshRepo, emailer, "local", clientCredsFlag) return api, emailer }
func TestCreateInvalidRequest(t *testing.T) { u := &url.URL{Scheme: "http", Host: "example.com", Path: "clients"} h := http.Header{"Content-Type": []string{"application/json"}} dbm := db.NewMemDB() repo := db.NewClientRepo(dbm) manager := manager.NewClientManager(repo, db.TransactionFactory(dbm), manager.ManagerOptions{}) res := &clientResource{manager: manager} tests := []struct { req *http.Request wantCode int wantBody string }{ // invalid content-type { req: &http.Request{Method: "POST", URL: u, Header: http.Header{"Content-Type": []string{"application/xml"}}}, wantCode: http.StatusBadRequest, wantBody: `{"error":"invalid_request","error_description":"unsupported content-type"}`, }, // invalid method { req: &http.Request{Method: "DELETE", URL: u, Header: h}, wantCode: http.StatusMethodNotAllowed, wantBody: `{"error":"invalid_request","error_description":"HTTP DELETE method not supported for this resource"}`, }, // invalid method { req: &http.Request{Method: "PUT", URL: u, Header: h}, wantCode: http.StatusMethodNotAllowed, wantBody: `{"error":"invalid_request","error_description":"HTTP PUT method not supported for this resource"}`, }, // invalid method { req: &http.Request{Method: "HEAD", URL: u, Header: h}, wantCode: http.StatusMethodNotAllowed, wantBody: `{"error":"invalid_request","error_description":"HTTP HEAD method not supported for this resource"}`, }, // unserializable body { req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody("asdf")}, wantCode: http.StatusBadRequest, wantBody: `{"error":"invalid_request","error_description":"unable to decode request body"}`, }, // empty body { req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody("")}, wantCode: http.StatusBadRequest, wantBody: `{"error":"invalid_request","error_description":"unable to decode request body"}`, }, // missing url field { req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"id":"foo"}`)}, wantCode: http.StatusBadRequest, wantBody: `{"error":"invalid_client_metadata","error_description":"zero redirect URLs"}`, }, // empty url array { req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":[]}`)}, wantCode: http.StatusBadRequest, wantBody: `{"error":"invalid_client_metadata","error_description":"zero redirect URLs"}`, }, // array with empty string { req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":[""]}`)}, wantCode: http.StatusBadRequest, wantBody: `{"error":"invalid_client_metadata","error_description":"missing or invalid field: redirectURIs"}`, }, // uri with unusable scheme { req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["asdf.com"]}`)}, wantCode: http.StatusBadRequest, wantBody: `{"error":"invalid_client_metadata","error_description":"no host for uri field redirect_uris"}`, }, // uri missing host { req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["http://"]}`)}, wantCode: http.StatusBadRequest, wantBody: `{"error":"invalid_client_metadata","error_description":"no host for uri field redirect_uris"}`, }, } for i, tt := range tests { w := httptest.NewRecorder() res.ServeHTTP(w, tt.req) if w.Code != tt.wantCode { t.Errorf("case %d: invalid response code, want=%d, got=%d", i, tt.wantCode, w.Code) } gotBody := w.Body.String() if gotBody != tt.wantBody { t.Errorf("case %d: invalid response body, want=%s, got=%s", i, tt.wantBody, gotBody) } } }
func main() { fs := flag.NewFlagSet("dex-overlord", flag.ExitOnError) keySecrets := pflag.NewBase64List(32) fs.Var(keySecrets, "key-secrets", "A comma-separated list of base64 encoded 32 byte strings used as symmetric keys used to encrypt/decrypt signing key data in DB. The first key is considered the active key and used for encryption, while the others are used to decrypt.") useOldFormat := fs.Bool("use-deprecated-secret-format", false, "In prior releases, the database used AES-CBC to encrypt keys. New deployments should use the default AES-GCM encryption.") dbURL := fs.String("db-url", "", "DSN-formatted database connection string") dbMigrate := fs.Bool("db-migrate", true, "perform database migrations when starting up overlord. This includes the initial DB objects creation.") keyPeriod := fs.Duration("key-period", 24*time.Hour, "length of time for-which a given key will be valid") gcInterval := fs.Duration("gc-interval", time.Hour, "length of time between garbage collection runs") adminListen := fs.String("admin-listen", "http://127.0.0.1:5557", "scheme, host and port for listening for administrative operation requests ") adminAPISecret := pflag.NewBase64(server.AdminAPISecretLength) fs.Var(adminAPISecret, "admin-api-secret", fmt.Sprintf("A base64-encoded %d byte string which is used to protect the Admin API.", server.AdminAPISecretLength)) localConnectorID := fs.String("local-connector", "local", "ID of the local connector") logDebug := fs.Bool("log-debug", false, "log debug-level information") logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps") printVersion := fs.Bool("version", false, "Print the version and exit") if err := fs.Parse(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if err := pflag.SetFlagsFromEnv(fs, "DEX_OVERLORD"); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if *printVersion { fmt.Printf("dex version %s\ngo version %s\n", strings.TrimPrefix(version, "v"), strings.TrimPrefix(runtime.Version(), "go")) os.Exit(0) } if *logDebug { log.EnableDebug() } if *logTimestamps { log.EnableTimestamps() } adminURL, err := url.Parse(*adminListen) if err != nil { log.Fatalf("Unable to use --admin-listen flag: %v", err) } if len(keySecrets.BytesSlice()) == 0 { log.Fatalf("Must specify at least one key secret") } dbCfg := db.Config{ DSN: *dbURL, MaxIdleConnections: 1, MaxOpenConnections: 1, } dbc, err := db.NewConnection(dbCfg) if err != nil { log.Fatalf(err.Error()) } if _, ok := dbc.Dialect.(gorp.PostgresDialect); !ok { log.Fatal("only postgres backend supported for multi server configurations") } if *dbMigrate { var sleep time.Duration for { var err error var migrations int if migrations, err = db.MigrateToLatest(dbc); err == nil { log.Infof("Performed %d db migrations", migrations) break } sleep = ptime.ExpBackoff(sleep, time.Minute) log.Errorf("Unable to migrate database, retrying in %v: %v", sleep, err) time.Sleep(sleep) } } userRepo := db.NewUserRepo(dbc) pwiRepo := db.NewPasswordInfoRepo(dbc) connCfgRepo := db.NewConnectorConfigRepo(dbc) clientRepo := db.NewClientRepo(dbc) userManager := manager.NewUserManager(userRepo, pwiRepo, connCfgRepo, db.TransactionFactory(dbc), manager.ManagerOptions{}) clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbc), clientmanager.ManagerOptions{}) connectorConfigRepo := db.NewConnectorConfigRepo(dbc) adminAPI := admin.NewAdminAPI(userRepo, pwiRepo, clientRepo, connectorConfigRepo, userManager, clientManager, *localConnectorID) kRepo, err := db.NewPrivateKeySetRepo(dbc, *useOldFormat, keySecrets.BytesSlice()...) if err != nil { log.Fatalf(err.Error()) } var sleep time.Duration for { var done bool _, err := kRepo.Get() switch err { case nil: done = true case key.ErrorNoKeys: done = true case db.ErrorCannotDecryptKeys: log.Fatalf("Cannot decrypt keys using any of the given key secrets. The key secrets must be changed to include one that can decrypt the existing keys, or the existing keys must be deleted.") } if done { break } sleep = ptime.ExpBackoff(sleep, time.Minute) log.Errorf("Unable to get keys from repository, retrying in %v: %v", sleep, err) time.Sleep(sleep) } krot := key.NewPrivateKeyRotator(kRepo, *keyPeriod) s := server.NewAdminServer(adminAPI, krot, adminAPISecret.String()) h := s.HTTPHandler() httpsrv := &http.Server{ Addr: adminURL.Host, Handler: h, } gc := db.NewGarbageCollector(dbc, *gcInterval) log.Infof("Binding to %s...", httpsrv.Addr) go func() { log.Fatal(httpsrv.ListenAndServe()) }() gc.Run() <-krot.Run() }
func TestClientToken(t *testing.T) { now := time.Now() tomorrow := now.Add(24 * time.Hour) clientMetadata := oidc.ClientMetadata{ RedirectURIs: []url.URL{ {Scheme: "https", Host: "authn.example.com", Path: "/callback"}, }, } dbm := db.NewMemDB() clientRepo := db.NewClientRepo(dbm) clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbm), clientmanager.ManagerOptions{}) cli := client.Client{ Metadata: clientMetadata, } creds, err := clientManager.New(cli) if err != nil { t.Fatalf("Failed to create client: %v", err) } validClientID := creds.ID privKey, err := key.GeneratePrivateKey() if err != nil { t.Fatalf("Failed to generate private key, error=%v", err) } signer := privKey.Signer() pubKey := *key.NewPublicKey(privKey.JWK()) validIss := "https://example.com" makeToken := func(iss, sub, aud string, iat, exp time.Time) string { claims := oidc.NewClaims(iss, sub, aud, iat, exp) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { t.Fatalf("Failed to generate JWT, error=%v", err) } return jwt.Encode() } validJWT := makeToken(validIss, validClientID, validClientID, now, tomorrow) invalidJWT := makeToken("", "", "", now, tomorrow) tests := []struct { keys []key.PublicKey manager *clientmanager.ClientManager header string wantCode int }{ // valid token { keys: []key.PublicKey{pubKey}, manager: clientManager, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusOK, }, // invalid token { keys: []key.PublicKey{pubKey}, manager: clientManager, header: fmt.Sprintf("BEARER %s", invalidJWT), wantCode: http.StatusUnauthorized, }, // empty header { keys: []key.PublicKey{pubKey}, manager: clientManager, header: "", wantCode: http.StatusUnauthorized, }, // unparsable token { keys: []key.PublicKey{pubKey}, manager: clientManager, header: "BEARER xxx", wantCode: http.StatusUnauthorized, }, // no verification keys { keys: []key.PublicKey{}, manager: clientManager, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // nil repo { keys: []key.PublicKey{pubKey}, manager: nil, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // empty repo { keys: []key.PublicKey{pubKey}, manager: clientmanager.NewClientManager(db.NewClientRepo(db.NewMemDB()), db.TransactionFactory(db.NewMemDB()), clientmanager.ManagerOptions{}), header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // client not in repo { keys: []key.PublicKey{pubKey}, manager: clientManager, header: fmt.Sprintf("BEARER %s", makeToken(validIss, "DOESNT-EXIST", "DOESNT-EXIST", now, tomorrow)), wantCode: http.StatusUnauthorized, }, } for i, tt := range tests { w := httptest.NewRecorder() mw := &clientTokenMiddleware{ issuerURL: validIss, ciManager: tt.manager, keysFunc: func() ([]key.PublicKey, error) { return tt.keys, nil }, next: staticHandler{}, } req := &http.Request{ Header: http.Header{ "Authorization": []string{tt.header}, }, } mw.ServeHTTP(w, req) if tt.wantCode != w.Code { t.Errorf("case %d: invalid response code, want=%d, got=%d", i, tt.wantCode, w.Code) } } }
func makeTestFixturesWithOptions(options testFixtureOptions) (*testFixtures, error) { dbMap := db.NewMemDB() userRepo, err := db.NewUserRepoFromUsers(dbMap, testUsers) if err != nil { return nil, err } pwRepo, err := db.NewPasswordInfoRepoFromPasswordInfos(dbMap, testPasswordInfos) if err != nil { return nil, err } connConfigs := []connector.ConnectorConfig{ &connector.OIDCConnectorConfig{ ID: testConnectorIDOpenID, IssuerURL: testIssuerURL.String(), ClientID: "12345", ClientSecret: "567789", }, &connector.OIDCConnectorConfig{ ID: testConnectorIDOpenIDTrusted, IssuerURL: testIssuerURL.String(), ClientID: "12345-trusted", ClientSecret: "567789-trusted", TrustedEmailProvider: true, }, &connector.OIDCConnectorConfig{ ID: testConnectorID1, IssuerURL: testIssuerURL.String(), ClientID: testConnectorID1 + "_client_id", ClientSecret: testConnectorID1 + "_client_secret", TrustedEmailProvider: true, }, &connector.LocalConnectorConfig{ ID: testConnectorLocalID, }, } connCfgRepo := db.NewConnectorConfigRepo(dbMap) if err := connCfgRepo.Set(connConfigs); err != nil { return nil, err } userManager := usermanager.NewUserManager(userRepo, pwRepo, connCfgRepo, db.TransactionFactory(dbMap), usermanager.ManagerOptions{}) sessionManager := sessionmanager.NewSessionManager(db.NewSessionRepo(db.NewMemDB()), db.NewSessionKeyRepo(db.NewMemDB())) sessionManager.GenerateCode = sequentialGenerateCodeFunc() refreshTokenRepo := refreshtest.NewTestRefreshTokenRepo() emailer, err := email.NewTemplatizedEmailerFromGlobs( emailTemplatesLocation+"/*.txt", emailTemplatesLocation+"/*.html", &email.FakeEmailer{}, "*****@*****.**") if err != nil { return nil, err } var clients []client.LoadableClient if options.clients == nil { clients = testClients } else { clients = options.clients } clientIDGenerator := func(hostport string) (string, error) { return hostport, nil } secGen := func() ([]byte, error) { return []byte("secret"), nil } clientRepo, err := db.NewClientRepoFromClients(dbMap, clients) if err != nil { return nil, err } clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbMap), clientmanager.ManagerOptions{ClientIDGenerator: clientIDGenerator, SecretGenerator: secGen}) km := key.NewPrivateKeyManager() err = km.Set(key.NewPrivateKeySet([]*key.PrivateKey{testPrivKey}, time.Now().Add(time.Minute))) if err != nil { return nil, err } tpl, err := getTemplates("dex", "https://coreos.com", "https://coreos.com/assets/images/brand/coreos-mark-30px.png", true, templatesLocation) if err != nil { return nil, err } srv := &Server{ IssuerURL: testIssuerURL, SessionManager: sessionManager, ClientRepo: clientRepo, Templates: tpl, UserRepo: userRepo, PasswordInfoRepo: pwRepo, UserManager: userManager, ClientManager: clientManager, KeyManager: km, RefreshTokenRepo: refreshTokenRepo, } err = setTemplates(srv, tpl) if err != nil { return nil, err } for _, config := range connConfigs { if err := srv.AddConnector(config); err != nil { return nil, err } } srv.UserEmailer = useremail.NewUserEmailer(srv.UserRepo, srv.PasswordInfoRepo, srv.KeyManager.Signer, srv.SessionManager.ValidityWindow, srv.IssuerURL, emailer, srv.absURL(httpPathResetPassword), srv.absURL(httpPathEmailVerify), srv.absURL(httpPathAcceptInvitation), ) clientCreds := map[string]oidc.ClientCredentials{} for _, c := range clients { clientCreds[c.Client.Credentials.ID] = c.Client.Credentials } return &testFixtures{ srv: srv, redirectURL: testRedirectURL, userRepo: userRepo, sessionManager: sessionManager, emailer: emailer, clientRepo: clientRepo, clientManager: clientManager, clientCreds: clientCreds, }, nil }