diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e27e3a916b..1e4b6dbd1bc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,6 +40,7 @@ jobs: - run: go test -race -short $(go list ./... | grep -v cmd) - run: ./scripts/test-e2e-jwt.sh - run: ./scripts/test-e2e-opaque.sh + - run: ./scripts/test-plugin.sh - run: test -z "$CIRCLE_PR_NUMBER" && goveralls -service=circle-ci -coverprofile=coverage.txt -repotoken=$COVERALLS_REPO_TOKEN || echo "forks are not allowed to push to coveralls" swagger: diff --git a/cmd/server/handler.go b/cmd/server/handler.go index 7effb3b8252..8f8af22848c 100644 --- a/cmd/server/handler.go +++ b/cmd/server/handler.go @@ -49,8 +49,6 @@ import ( var _ = &consent.Handler{} -var errNilDependency = errors.New("A dependency was expected to be defined but is nil. Please open an issue with the stack trace.") - func RunHost(c *config.Config) func(cmd *cobra.Command, args []string) { return func(cmd *cobra.Command, args []string) { fmt.Println(banner) diff --git a/cmd/server/handler_client_factory.go b/cmd/server/handler_client_factory.go index b84c7df08a4..de3c1efefdb 100644 --- a/cmd/server/handler_client_factory.go +++ b/cmd/server/handler_client_factory.go @@ -27,33 +27,11 @@ import ( "github.com/ory/herodot" "github.com/ory/hydra/client" "github.com/ory/hydra/config" - "github.com/ory/sqlcon" ) func newClientManager(c *config.Config) client.Manager { ctx := c.Context() - - switch con := ctx.Connection.(type) { - case *config.MemoryConnection: - expectDependency(c.GetLogger(), ctx.Hasher) - return client.NewMemoryManager(ctx.Hasher) - case *sqlcon.SQLConnection: - expectDependency(c.GetLogger(), ctx.Hasher, con.GetDatabase()) - return &client.SQLManager{ - DB: con.GetDatabase(), - Hasher: ctx.Hasher, - } - case *config.PluginConnection: - if m, err := con.NewClientManager(); err != nil { - c.GetLogger().Fatalf("Could not load client manager plugin %s", err) - } else { - return m - } - break - default: - panic("Unknown connection type.") - } - return nil + return ctx.Connection.NewClientManager(ctx.Hasher) } func newClientHandler(c *config.Config, router *httprouter.Router, manager client.Manager) *client.Handler { diff --git a/cmd/server/handler_consent_factory.go b/cmd/server/handler_consent_factory.go index c4047f4bf65..c6697e37aa1 100644 --- a/cmd/server/handler_consent_factory.go +++ b/cmd/server/handler_consent_factory.go @@ -26,37 +26,11 @@ import ( "github.com/ory/hydra/client" "github.com/ory/hydra/config" "github.com/ory/hydra/consent" - "github.com/ory/sqlcon" ) func injectConsentManager(c *config.Config, cm client.Manager) { var ctx = c.Context() - var manager consent.Manager - - switch con := ctx.Connection.(type) { - case *config.MemoryConnection: - expectDependency(c.GetLogger(), ctx.FositeStore) - manager = consent.NewMemoryManager(ctx.FositeStore) - break - case *sqlcon.SQLConnection: - expectDependency(c.GetLogger(), ctx.FositeStore, con.GetDatabase()) - manager = consent.NewSQLManager( - con.GetDatabase(), - cm, - ctx.FositeStore, - ) - break - case *config.PluginConnection: - var err error - if manager, err = con.NewConsentManager(); err != nil { - c.GetLogger().Fatalf("Could not load client manager plugin %s", err) - } - break - default: - panic("Unknown connection type.") - } - - ctx.ConsentManager = manager + ctx.ConsentManager = ctx.Connection.NewConsentManager(cm, ctx.FositeStore) } func newConsentHandler(c *config.Config, router *httprouter.Router) *consent.Handler { diff --git a/cmd/server/handler_health_factory.go b/cmd/server/handler_health_factory.go index 6b502c3e849..b8a4574a4c3 100644 --- a/cmd/server/handler_health_factory.go +++ b/cmd/server/handler_health_factory.go @@ -25,33 +25,11 @@ import ( "github.com/ory/herodot" "github.com/ory/hydra/config" "github.com/ory/hydra/health" - "github.com/ory/sqlcon" ) func newHealthHandler(c *config.Config, router *httprouter.Router) *health.Handler { ctx := c.Context() - var rc health.ReadyChecker - - switch con := ctx.Connection.(type) { - case *config.MemoryConnection: - rc = func() error { - return nil - } - break - case *sqlcon.SQLConnection: - expectDependency(c.GetLogger(), con.GetDatabase()) - rc = func() error { - return con.GetDatabase().Ping() - } - break - case *config.PluginConnection: - rc = func() error { - return con.Ping() - } - break - default: - panic("Unknown connection type.") - } + var rc health.ReadyChecker = ctx.Connection.Ping w := herodot.NewJSONWriter(c.GetLogger()) w.ErrorEnhancer = writerErrorEnhancer diff --git a/cmd/server/handler_jwk_factory.go b/cmd/server/handler_jwk_factory.go index 1d8d8ac7ca8..d9b33c52f6b 100644 --- a/cmd/server/handler_jwk_factory.go +++ b/cmd/server/handler_jwk_factory.go @@ -26,35 +26,14 @@ import ( "github.com/ory/hydra/config" "github.com/ory/hydra/jwk" "github.com/ory/hydra/oauth2" - "github.com/ory/sqlcon" ) func injectJWKManager(c *config.Config) { ctx := c.Context() - switch con := ctx.Connection.(type) { - case *config.MemoryConnection: - ctx.KeyManager = &jwk.MemoryManager{} - break - case *sqlcon.SQLConnection: - expectDependency(c.GetLogger(), con.GetDatabase()) - ctx.KeyManager = &jwk.SQLManager{ - DB: con.GetDatabase(), - Cipher: &jwk.AEAD{ - Key: c.GetSystemSecret(), - }, - } - break - case *config.PluginConnection: - var err error - ctx.KeyManager, err = con.NewJWKManager() - if err != nil { - c.GetLogger().Fatalf("Could not load client manager plugin %s", err) - } - break - default: - c.GetLogger().Fatalf("Unknown connection type.") - } + ctx.KeyManager = ctx.Connection.NewJWKManager(&jwk.AEAD{ + Key: c.GetSystemSecret(), + }) } func newJWKHandler(c *config.Config, router *httprouter.Router) *jwk.Handler { diff --git a/cmd/server/handler_oauth2_factory.go b/cmd/server/handler_oauth2_factory.go index 1aa5134aab4..47907063957 100644 --- a/cmd/server/handler_oauth2_factory.go +++ b/cmd/server/handler_oauth2_factory.go @@ -39,33 +39,12 @@ import ( "github.com/ory/hydra/jwk" "github.com/ory/hydra/oauth2" "github.com/ory/hydra/pkg" - "github.com/ory/sqlcon" "github.com/pborman/uuid" ) func injectFositeStore(c *config.Config, clients client.Manager) { var ctx = c.Context() - var store pkg.FositeStorer - - switch con := ctx.Connection.(type) { - case *config.MemoryConnection: - store = oauth2.NewFositeMemoryStore(clients, c.GetAccessTokenLifespan()) - break - case *sqlcon.SQLConnection: - expectDependency(c.GetLogger(), con.GetDatabase()) - store = oauth2.NewFositeSQLStore(clients, con.GetDatabase(), c.GetLogger(), c.GetAccessTokenLifespan(), c.OAuth2AccessTokenStrategy == "jwt") - break - case *config.PluginConnection: - var err error - if store, err = con.NewOAuth2Manager(clients); err != nil { - c.GetLogger().Fatalf("Could not load client manager plugin %s", err) - } - break - default: - panic("Unknown connection type.") - } - - ctx.FositeStore = store + ctx.FositeStore = ctx.Connection.NewOAuth2Manager(clients, c.GetAccessTokenLifespan(), c.OAuth2AccessTokenStrategy) } func newOAuth2Provider(c *config.Config) fosite.OAuth2Provider { diff --git a/cmd/server/helper_deps.go b/cmd/server/helper_deps.go index 4601670bbfa..a48181bbade 100644 --- a/cmd/server/helper_deps.go +++ b/cmd/server/helper_deps.go @@ -25,6 +25,8 @@ import ( "github.com/sirupsen/logrus" ) +var errNilDependency = errors.New("A dependency was expected to be defined but is nil. Please open an issue with the stack trace.") + func expectDependency(logger logrus.FieldLogger, dependencies ...interface{}) { for _, d := range dependencies { if d == nil { diff --git a/config/backend_connector.go b/config/backend_connector.go new file mode 100644 index 00000000000..e3a22b3132b --- /dev/null +++ b/config/backend_connector.go @@ -0,0 +1,80 @@ +/* + * Copyright © 2015-2018 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2015-2018 Aeneas Rekkas + * @license Apache-2.0 + */ + +package config + +import ( + "sync" + "time" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/ory/fosite" + "github.com/ory/hydra/client" + "github.com/ory/hydra/consent" + "github.com/ory/hydra/jwk" + "github.com/ory/hydra/pkg" +) + +var ( + backends = make(map[string]BackendConnector) + bmutex sync.Mutex + errNilDependency = errors.New("A dependency was expected to be defined but is nil. Please open an issue with the stack trace.") +) + +type BackendConnector interface { + Init(url string, l logrus.FieldLogger) error + NewConsentManager(clientManager client.Manager, fs pkg.FositeStorer) consent.Manager + NewOAuth2Manager(clientManager client.Manager, accessTokenLifespan time.Duration, tokenStrategy string) pkg.FositeStorer + NewClientManager(hasher fosite.Hasher) client.Manager + NewJWKManager(cipher *jwk.AEAD) jwk.Manager + Ping() error + Prefixes() []string +} + +func RegisterBackend(b BackendConnector) { + bmutex.Lock() + for _, prefix := range b.Prefixes() { + backends[prefix] = b + } + bmutex.Unlock() +} + +func supportedSchemes() []string { + keys := make([]string, len(backends)) + i := 0 + for k := range backends { + keys[i] = k + i++ + } + return keys +} + +func expectDependency(logger logrus.FieldLogger, dependencies ...interface{}) { + if logger == nil { + panic("missing logger for dependency check") + } + for _, d := range dependencies { + if d == nil { + logger.WithError(errors.WithStack(errNilDependency)).Fatalf("A fatal issue occurred.") + } + } +} diff --git a/config/backend_memory.go b/config/backend_memory.go index 8d38ecc2e06..d30deae10f9 100644 --- a/config/backend_memory.go +++ b/config/backend_memory.go @@ -20,4 +20,55 @@ package config -type MemoryConnection struct{} +import ( + "time" + + "github.com/sirupsen/logrus" + + "github.com/ory/fosite" + "github.com/ory/hydra/client" + "github.com/ory/hydra/consent" + "github.com/ory/hydra/jwk" + "github.com/ory/hydra/oauth2" + "github.com/ory/hydra/pkg" +) + +type MemoryBackend struct { + l logrus.FieldLogger +} + +func init() { + RegisterBackend(&MemoryBackend{}) +} + +func (m *MemoryBackend) Init(url string, l logrus.FieldLogger) error { + m.l = l + return nil +} + +func (m *MemoryBackend) NewConsentManager(_ client.Manager, fs pkg.FositeStorer) consent.Manager { + expectDependency(m.l, fs) + return consent.NewMemoryManager(fs) +} + +func (m *MemoryBackend) NewOAuth2Manager(clientManager client.Manager, accessTokenLifespan time.Duration, _ string) pkg.FositeStorer { + expectDependency(m.l, clientManager) + return oauth2.NewFositeMemoryStore(clientManager, accessTokenLifespan) +} + +func (m *MemoryBackend) NewClientManager(hasher fosite.Hasher) client.Manager { + expectDependency(m.l, hasher) + return client.NewMemoryManager(hasher) +} + +func (m *MemoryBackend) NewJWKManager(_ *jwk.AEAD) jwk.Manager { + return &jwk.MemoryManager{} +} + +func (m *MemoryBackend) Prefixes() []string { + return []string{"memory"} +} + +func (m *MemoryBackend) Ping() error { + return nil +} diff --git a/config/backend_plugin.go b/config/backend_plugin.go index 875c8a35ab3..ee78fbb7a7a 100644 --- a/config/backend_plugin.go +++ b/config/backend_plugin.go @@ -23,12 +23,6 @@ package config import ( "plugin" - "github.com/jmoiron/sqlx" - "github.com/ory/fosite" - "github.com/ory/hydra/client" - "github.com/ory/hydra/consent" - "github.com/ory/hydra/jwk" - "github.com/ory/hydra/pkg" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -37,8 +31,8 @@ type PluginConnection struct { Config *Config plugin *plugin.Plugin didConnect bool + connector BackendConnector Logger logrus.FieldLogger - db *sqlx.DB } func (c *PluginConnection) load() error { @@ -56,11 +50,7 @@ func (c *PluginConnection) load() error { return nil } -func (c *PluginConnection) Ping() error { - return c.db.Ping() -} - -func (c *PluginConnection) Connect() error { +func (c *PluginConnection) Load() error { cf := c.Config if c.didConnect { return nil @@ -70,80 +60,15 @@ func (c *PluginConnection) Connect() error { return errors.WithStack(err) } - if l, err := c.plugin.Lookup("Connect"); err != nil { - return errors.Wrap(err, "Unable to look up `Connect`") - } else if con, ok := l.(func(url string) (*sqlx.DB, error)); !ok { - return errors.New("Unable to type assert `Connect`") + if l, err := c.plugin.Lookup("BackendConnector"); err != nil { + return errors.Wrap(err, "Unable to look up `BackendConnector`") + } else if connector, ok := l.(*BackendConnector); !ok { + return errors.New("Unable to type assert `BackendConnector`") } else { - if db, err := con(cf.DatabaseURL); err != nil { - return errors.Wrap(err, "Could not connect to database") - } else { - cf.GetLogger().Info("Successfully connected through database plugin") - c.db = db - cf.GetLogger().Debugf("Address of database plugin is: %s", c.db) - if err := db.Ping(); err != nil { - cf.GetLogger().WithError(err).Fatal("Could not ping database connection from plugin") - } - } + cf.GetLogger().Info("Successfully loaded database plugin") + c.connector = *connector + cf.GetLogger().Debugf("Address of database plugin is: %p", connector) + RegisterBackend(c.connector) } return nil } - -func (c *PluginConnection) NewClientManager() (client.Manager, error) { - if err := c.load(); err != nil { - return nil, errors.WithStack(err) - } - - ctx := c.Config.Context() - if l, err := c.plugin.Lookup("NewClientManager"); err != nil { - return nil, errors.Wrap(err, "Unable to look up `NewClientManager`") - } else if m, ok := l.(func(*sqlx.DB, fosite.Hasher) client.Manager); !ok { - return nil, errors.New("Unable to type assert `NewClientManager`") - } else { - return m(c.db, ctx.Hasher), nil - } -} - -func (c *PluginConnection) NewJWKManager() (jwk.Manager, error) { - if err := c.load(); err != nil { - return nil, errors.WithStack(err) - } - - if l, err := c.plugin.Lookup("NewJWKManager"); err != nil { - return nil, errors.Wrap(err, "Unable to look up `NewJWKManager`") - } else if m, ok := l.(func(*sqlx.DB, *jwk.AEAD) jwk.Manager); !ok { - return nil, errors.New("Unable to type assert `NewJWKManager`") - } else { - return m(c.db, &jwk.AEAD{ - Key: c.Config.GetSystemSecret(), - }), nil - } -} - -func (c *PluginConnection) NewOAuth2Manager(clientManager client.Manager) (pkg.FositeStorer, error) { - if err := c.load(); err != nil { - return nil, errors.WithStack(err) - } - - if l, err := c.plugin.Lookup("NewOAuth2Manager"); err != nil { - return nil, errors.Wrap(err, "Unable to look up `NewOAuth2Manager`") - } else if m, ok := l.(func(*sqlx.DB, client.Manager, logrus.FieldLogger) pkg.FositeStorer); !ok { - return nil, errors.New("Unable to type assert `NewOAuth2Manager`") - } else { - return m(c.db, clientManager, c.Config.GetLogger()), nil - } -} - -func (c *PluginConnection) NewConsentManager() (consent.Manager, error) { - if err := c.load(); err != nil { - return nil, errors.WithStack(err) - } - - if l, err := c.plugin.Lookup("NewConsentManager"); err != nil { - return nil, errors.Wrap(err, "Unable to look up `NewConsentManager`") - } else if m, ok := l.(func(*sqlx.DB) consent.Manager); !ok { - return nil, errors.Errorf("Unable to type assert `NewConsentManager`, got %v", l) - } else { - return m(c.db), nil - } -} diff --git a/config/backend_sql.go b/config/backend_sql.go new file mode 100644 index 00000000000..16de50bc9d8 --- /dev/null +++ b/config/backend_sql.go @@ -0,0 +1,94 @@ +/* + * Copyright © 2015-2018 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2015-2018 Aeneas Rekkas + * @license Apache-2.0 + */ + +package config + +import ( + "time" + + "github.com/jmoiron/sqlx" + "github.com/sirupsen/logrus" + + "github.com/ory/fosite" + "github.com/ory/hydra/client" + "github.com/ory/hydra/consent" + "github.com/ory/hydra/jwk" + "github.com/ory/hydra/oauth2" + "github.com/ory/hydra/pkg" + "github.com/ory/sqlcon" +) + +type SQLBackend struct { + db *sqlx.DB + l logrus.FieldLogger +} + +func init() { + RegisterBackend(&SQLBackend{}) +} + +func (s *SQLBackend) Init(url string, l logrus.FieldLogger) error { + connection, err := sqlcon.NewSQLConnection(url, l) + if err != nil { + return err + } + s.l = l + s.db = connection.GetDatabase() + return nil +} + +func (s *SQLBackend) NewConsentManager(clientManager client.Manager, fs pkg.FositeStorer) consent.Manager { + expectDependency(s.l, clientManager, s.db, fs) + return consent.NewSQLManager( + s.db, + clientManager, + fs, + ) +} + +func (s *SQLBackend) NewOAuth2Manager(clientManager client.Manager, accessTokenLifespan time.Duration, tokenStrategy string) pkg.FositeStorer { + expectDependency(s.l, clientManager, s.db) + return oauth2.NewFositeSQLStore(clientManager, s.db, s.l, accessTokenLifespan, tokenStrategy == "jwt") +} + +func (s *SQLBackend) NewClientManager(hasher fosite.Hasher) client.Manager { + expectDependency(s.l, hasher, s.db) + return &client.SQLManager{ + DB: s.db, + Hasher: hasher, + } +} + +func (s *SQLBackend) NewJWKManager(cipher *jwk.AEAD) jwk.Manager { + expectDependency(s.l, cipher, s.db) + return &jwk.SQLManager{ + DB: s.db, + Cipher: cipher, + } +} + +func (s *SQLBackend) Prefixes() []string { + return []string{"mysql", "postgres"} +} + +func (s *SQLBackend) Ping() error { + expectDependency(s.l, s.db) + return s.db.Ping() +} diff --git a/config/backend_test.go b/config/backend_test.go new file mode 100644 index 00000000000..ff1375733fd --- /dev/null +++ b/config/backend_test.go @@ -0,0 +1,131 @@ +/* + * Copyright © 2015-2018 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2015-2018 Aeneas Rekkas + * @license Apache-2.0 + */ +package config + +import ( + "flag" + "fmt" + "log" + "os" + "strings" + "testing" + "time" + + "github.com/ory/fosite" + "github.com/ory/hydra/client" + "github.com/ory/hydra/jwk" + "github.com/ory/hydra/pkg" + "github.com/sirupsen/logrus" +) + +type testCase struct { + name string + b BackendConnector + u string +} + +var ( + tests []testCase = []testCase{ + { + "memory", + &MemoryBackend{}, + "memory", + }, + } + l = logrus.New() + hasher = &fosite.BCrypt{WorkFactor: 8} + encryptionKey, _ = jwk.RandomBytes(32) + cipher = &jwk.AEAD{Key: encryptionKey} +) + +func TestMain(m *testing.M) { + flag.Parse() + if !testing.Short() { + if uri := os.Getenv("TEST_DATABASE_POSTGRESQL"); uri != "" { + tests = append(tests, testCase{"postgresql", &SQLBackend{}, uri}) + } else { + log.Println("Did not find postgresql test database config, skipping backend connector test") + } + + if uri := os.Getenv("TEST_DATABASE_MYSQL"); uri != "" { + if !strings.HasPrefix(uri, "mysql") { + uri = fmt.Sprintf("mysql://%s", uri) + } + tests = append(tests, testCase{"mysql", &SQLBackend{}, uri}) + } else { + log.Println("Did not find mysql test database config, skipping backend connector test") + } + } + + os.Exit(m.Run()) +} + +func TestBackendConnectors(t *testing.T) { + for _, tc := range tests { + var cm client.Manager + var fs pkg.FositeStorer + + t.Run(fmt.Sprintf("%s/Init", tc.name), func(t *testing.T) { + if err := tc.b.Init(tc.u, l); err != nil { + t.Fatalf("could not initialize backend due to error: %v", err) + } + }) + + t.Run(fmt.Sprintf("%s/Ping", tc.name), func(t *testing.T) { + if err := tc.b.Ping(); err != nil { + t.Errorf("could not ping backend due to error: %v", err) + } + }) + + t.Run(fmt.Sprintf("%s/NewClientManager", tc.name), func(t *testing.T) { + if cm = tc.b.NewClientManager(hasher); cm == nil { + t.Errorf("expected non-nil result") + } + }) + + t.Run(fmt.Sprintf("%s/NewOAuth2Manager", tc.name), func(t *testing.T) { + if fs = tc.b.NewOAuth2Manager(cm, time.Hour, "opaque"); fs == nil { + t.Errorf("expected non-nil result") + } + }) + + t.Run(fmt.Sprintf("%s/NewConsentManager", tc.name), func(t *testing.T) { + if want := tc.b.NewConsentManager(cm, fs); want == nil { + t.Errorf("expected non-nil result") + } + }) + + t.Run(fmt.Sprintf("%s/NewJWKManager", tc.name), func(t *testing.T) { + if want := tc.b.NewJWKManager(cipher); want == nil { + t.Errorf("expected non-nil result") + } + }) + + t.Run(fmt.Sprintf("%s/Prefixes", tc.name), func(t *testing.T) { + prefixes := tc.b.Prefixes() + for _, prefix := range prefixes { + if strings.HasPrefix(tc.u, prefix) { + return + } + } + t.Errorf("did not find matching prefix for given backend uri") + }) + } +} diff --git a/config/config.go b/config/config.go index 11b1d47f22b..083a3f14b6a 100644 --- a/config/config.go +++ b/config/config.go @@ -40,7 +40,6 @@ import ( "github.com/ory/hydra/health" "github.com/ory/hydra/metrics/prometheus" "github.com/ory/hydra/pkg" - "github.com/ory/sqlcon" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -238,34 +237,34 @@ func (c *Config) Context() *Context { return c.context } - var connection interface{} = &MemoryConnection{} if c.DatabaseURL == "" { c.GetLogger().Fatalf(`DATABASE_URL is not set, use "export DATABASE_URL=memory" for an in memory storage or the documented database adapters.`) } else if c.DatabasePlugin != "" { c.GetLogger().Infof("Database plugin set to %s", c.DatabasePlugin) pc := &PluginConnection{Config: c, Logger: c.GetLogger()} - if err := pc.Connect(); err != nil { + if err := pc.Load(); err != nil { c.GetLogger().Fatalf("Could not connect via database plugin: %s", err) } - connection = pc - } else if c.DatabaseURL != "memory" { + } + + var connection BackendConnector + scheme := "memory" + if c.DatabaseURL != "memory" { u, err := url.Parse(c.DatabaseURL) if err != nil { c.GetLogger().Fatalf("Could not parse DATABASE_URL: %s", err) } - switch u.Scheme { - case "postgres": - fallthrough - case "mysql": - connection, err = sqlcon.NewSQLConnection(c.DatabaseURL, c.GetLogger()) - if err != nil { - c.GetLogger().WithError(err).Fatalf(`Unable to initialize SQL connection`) - } - break - default: - c.GetLogger().Fatalf(`Unknown DSN "%s" in DATABASE_URL: %s`, u.Scheme, c.DatabaseURL) + scheme = u.Scheme + } + + if backend, ok := backends[scheme]; ok { + if err := backend.Init(c.DatabaseURL, c.GetLogger()); err != nil { + c.GetLogger().Fatalf(`Could not connect to database backend: %s`, err) } + connection = backend + } else { + c.GetLogger().Fatalf(`Unknown DSN scheme "%s" in DATABASE_URL "%s", schemes %v supported`, scheme, c.DatabaseURL, supportedSchemes()) } c.context = &Context{ diff --git a/config/context.go b/config/context.go index 20eba0d9090..9d0977b0b79 100644 --- a/config/context.go +++ b/config/context.go @@ -29,7 +29,7 @@ import ( ) type Context struct { - Connection interface{} + Connection BackendConnector Hasher fosite.Hasher FositeStrategy oauth2.CoreStrategy diff --git a/scripts/test-plugin.sh b/scripts/test-plugin.sh new file mode 100755 index 00000000000..6af459e91b3 --- /dev/null +++ b/scripts/test-plugin.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$( dirname "${BASH_SOURCE[0]}" )/.." + +killall hydra || true + +export HYDRA_URL=http://127.0.0.1:4444/ +export OAUTH2_CLIENT_ID=foobar +export OAUTH2_CLIENT_SECRET=bazbar +export OAUTH2_ISSUER_URL=http://127.0.0.1:4444/ +export LOG_LEVEL=debug +export REDIRECT_URL=http://127.0.0.1:4445/callback +export AUTH2_SCOPE=openid,offline + +go install . +go build -buildmode=plugin -o memtest.so ./test/plugin + +DATABASE_URL=memtest:// \ + DATABASE_PLUGIN=memtest.so \ + OAUTH2_CONSENT_URL=http://127.0.0.1:3000/consent \ + OAUTH2_LOGIN_URL=http://127.0.0.1:3000/login \ + OAUTH2_ERROR_URL=http://127.0.0.1:3000/error \ + OAUTH2_SHARE_ERROR_DEBUG=true \ + OAUTH2_ACCESS_TOKEN_STRATEGY=jwt \ + hydra serve --dangerous-force-http --disable-telemetry & + +while ! echo exit | nc 127.0.0.1 4444; do sleep 1; done + + +hydra clients create \ + --endpoint http://127.0.0.1:4444 \ + --id $OAUTH2_CLIENT_ID \ + --secret $OAUTH2_CLIENT_SECRET \ + --response-types token,code,id_token \ + --grant-types refresh_token,authorization_code,client_credentials \ + --scope openid,offline \ + --callbacks http://127.0.0.1:4445/callback + +token=$(hydra token client) + +hydra token introspect "$token" + +hydra clients delete $OAUTH2_CLIENT_ID + +kill %1 + +rm memtest.so + +exit 0 + +sleep 5 diff --git a/test/plugin/memtest.go b/test/plugin/memtest.go new file mode 100644 index 00000000000..28a3b1cfde3 --- /dev/null +++ b/test/plugin/memtest.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/ory/hydra/config" +) + +type MemTestPlugin struct { + config.MemoryBackend +} + +func (m *MemTestPlugin) Prefixes() []string { + return []string{"memtest"} +} + +var BackendConnector config.BackendConnector = &MemTestPlugin{}