From 8b40a37a1c535eb1ca225c9d0634a821eed6fb53 Mon Sep 17 00:00:00 2001 From: Ales Stimec Date: Tue, 26 Nov 2024 16:35:42 +0100 Subject: [PATCH] refactor(internal/jimm/jimm.go): introduces a jimm constructor - adds jimm.New costructor - adds jimmtest.NewJIMM - refactors GetRoleManager and GetGroupManager --- cmd/jimmctl/cmd/relation_test.go | 4 +- cmd/jimmsrv/main.go | 26 +- cmd/jimmsrv/service/service.go | 196 ++++++----- cmd/jimmsrv/service/service_test.go | 80 +++-- docker-compose.yaml | 2 +- internal/db/cloud_test.go | 4 +- internal/db/cloudcredential_test.go | 2 +- internal/db/controller_test.go | 6 +- internal/db/identitymodeldefaults_test.go | 12 +- internal/db/model_test.go | 8 +- internal/jimm/access.go | 2 +- internal/jimm/access_test.go | 99 +----- internal/jimm/admin_test.go | 78 ++--- internal/jimm/applicationoffer_test.go | 233 ++++--------- internal/jimm/audit_log.go | 4 +- internal/jimm/audit_log_test.go | 2 +- internal/jimm/cloud.go | 2 +- internal/jimm/cloud_test.go | 221 ++++-------- internal/jimm/cloudcredential_test.go | 176 +++------- internal/jimm/clouddefaults_test.go | 32 +- internal/jimm/controller_test.go | 167 ++------- internal/jimm/export_test.go | 4 +- internal/jimm/group/group_test.go | 8 +- internal/jimm/identity_test.go | 53 +-- internal/jimm/identitymodeldefaults_test.go | 21 +- internal/jimm/jimm.go | 298 ++++++++++------ internal/jimm/jimm_test.go | 131 ++----- internal/jimm/model_status_parser_test.go | 17 +- internal/jimm/model_test.go | 320 +++++------------- internal/jimm/relation_test.go | 44 +-- internal/jimm/resource_test.go | 19 +- internal/jimm/service_account_test.go | 78 ++--- internal/jimm/user_test.go | 30 +- internal/jimm/watcher.go | 2 +- internal/jimm/watcher_test.go | 38 +-- internal/jimmhttp/rebac_admin/groups.go | 22 +- .../rebac_admin/groups_integration_test.go | 10 +- internal/jimmhttp/rebac_admin/groups_test.go | 14 +- internal/jimmhttp/rebac_admin/identities.go | 4 +- .../jimmhttp/rebac_admin/identities_test.go | 4 +- internal/jimmhttp/rebac_admin/roles.go | 16 +- .../rebac_admin/roles_integration_test.go | 6 +- internal/jimmhttp/rebac_admin/roles_test.go | 12 +- internal/jujuapi/access_control.go | 12 +- internal/jujuapi/admin_test.go | 4 +- internal/jujuapi/controllerroot.go | 4 +- internal/jujuapi/role.go | 12 +- internal/jujuapi/websocket.go | 2 +- internal/jujuclient/dial.go | 2 +- internal/testutils/cmdtest/jimmsuite.go | 1 + internal/testutils/jimmtest/env.go | 28 +- internal/testutils/jimmtest/fixture.go | 2 +- internal/testutils/jimmtest/jimm.go | 98 ++++++ internal/testutils/jimmtest/jimm_mock.go | 24 +- internal/testutils/jimmtest/suite.go | 122 ++++--- 55 files changed, 1071 insertions(+), 1747 deletions(-) create mode 100644 internal/testutils/jimmtest/jimm.go diff --git a/cmd/jimmctl/cmd/relation_test.go b/cmd/jimmctl/cmd/relation_test.go index b7d51183a..ac0dfd45c 100644 --- a/cmd/jimmctl/cmd/relation_test.go +++ b/cmd/jimmctl/cmd/relation_test.go @@ -339,7 +339,7 @@ func initializeEnvironment(c *gc.C, ctx context.Context, db *db.Database, u dbmo } func (s *relationSuite) TestListRelations(c *gc.C) { - env := initializeEnvironment(c, context.Background(), &s.JIMM.Database, *s.AdminUser) + env := initializeEnvironment(c, context.Background(), s.JIMM.Database, *s.AdminUser) bClient := s.SetupCLIAccess(c, "alice") // alice is superuser relations := []apiparams.RelationshipTuple{{ @@ -420,7 +420,7 @@ func (s *relationSuite) TestListRelations(c *gc.C) { } func (s *relationSuite) TestListRelationsWithError(c *gc.C) { - env := initializeEnvironment(c, context.Background(), &s.JIMM.Database, *s.AdminUser) + env := initializeEnvironment(c, context.Background(), s.JIMM.Database, *s.AdminUser) // alice is superuser bClient := s.SetupCLIAccess(c, "alice") diff --git a/cmd/jimmsrv/main.go b/cmd/jimmsrv/main.go index 30beec722..5624b807f 100644 --- a/cmd/jimmsrv/main.go +++ b/cmd/jimmsrv/main.go @@ -183,35 +183,13 @@ func start(ctx context.Context, s *service.Service) error { CorsAllowedOrigins: corsAllowedOrigins, LogSQL: logSQL, LogLevel: logLevel, + IsLeader: os.Getenv("JIMM_IS_LEADER") != "", }) if err != nil { return err } - isLeader := os.Getenv("JIMM_IS_LEADER") != "" - if isLeader { - s.Go(func() error { return jimmsvc.WatchControllers(ctx) }) // Deletes dead/dying models, updates model config. - } - s.Go(func() error { return jimmsvc.WatchModelSummaries(ctx) }) - - if isLeader { - zapctx.Info(ctx, "attempting to start JWKS rotator and generate OAuth secret key") - s.Go(func() error { - if err := jimmsvc.StartJWKSRotator(ctx, time.NewTicker(time.Hour).C, time.Now().UTC().AddDate(0, 3, 0)); err != nil { - zapctx.Error(ctx, "failed to start JWKS rotator", zap.Error(err)) - return err - } - return nil - }) - s.Go(func() error { - return jimmsvc.OpenFGACleanup(ctx, time.NewTicker(6*time.Hour).C) - }) - } - - if isLeader { - // No need for s.Go() since this routine doesn't return an error. - go jimmsvc.MonitorResources(ctx) - } + jimmsvc.StartServices(ctx, s) httpsrv := &http.Server{ Addr: addr, diff --git a/cmd/jimmsrv/service/service.go b/cmd/jimmsrv/service/service.go index 4c929b03b..83d9e6f6b 100644 --- a/cmd/jimmsrv/service/service.go +++ b/cmd/jimmsrv/service/service.go @@ -13,6 +13,7 @@ import ( "time" "github.com/antonlindstrom/pgstore" + service "github.com/canonical/go-service" cofga "github.com/canonical/ofga" "github.com/go-chi/chi/v5" chimiddleware "github.com/go-chi/chi/v5/middleware" @@ -27,13 +28,12 @@ import ( "gorm.io/gorm" "github.com/canonical/jimm/v3/internal/auth" + "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/discharger" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm" jimmcreds "github.com/canonical/jimm/v3/internal/jimm/credentials" - "github.com/canonical/jimm/v3/internal/jimm/group" - "github.com/canonical/jimm/v3/internal/jimm/role" "github.com/canonical/jimm/v3/internal/jimmhttp" "github.com/canonical/jimm/v3/internal/jimmhttp/rebac_admin" "github.com/canonical/jimm/v3/internal/jimmjwx" @@ -101,6 +101,9 @@ type Params struct { // is not set a random UUID will be generated. ControllerUUID string + // IsLeader indicates that this is the JIMM leader unit. + IsLeader bool + // DSN is the data source name that the JIMM service will use to // connect to its database. If this is empty an in-memory database // will be used. @@ -195,14 +198,17 @@ type Params struct { // A Service is the implementation of a JIMM server. type Service struct { - jimm jimm.JIMM + jimm *jimm.JIMM + + isLeader bool + auditLogCleanupPeriod int mux *chi.Mux cleanups []func() error } func (s *Service) JIMM() *jimm.JIMM { - return &s.jimm + return s.jimm } // ServeHTTP implements http.Handler. @@ -236,10 +242,6 @@ func (s *Service) WatchModelSummaries(ctx context.Context) error { // StartJWKSRotator see internal/jimmjwx/jwks.go for details. func (s *Service) StartJWKSRotator(ctx context.Context, checkRotateRequired <-chan time.Time, initialRotateRequiredTime time.Time) error { - if s.jimm.JWKService == nil { - zapctx.Warn(ctx, "not starting JWKS rotation") - return nil - } return s.jimm.JWKService.StartJWKSRotator(ctx, checkRotateRequired, initialRotateRequiredTime) } @@ -290,66 +292,52 @@ func (s *Service) AddCleanup(f func() error) { } // NewService creates a new Service using the given params. +// +//nolint:gocognit // NewService function to be ignored. func NewService(ctx context.Context, p Params) (*Service, error) { const op = errors.Op("NewService") s := new(Service) - s.mux = chi.NewRouter() - - s.mux.Use(chimiddleware.RequestLogger(&logger.HTTPLogFormatter{})) - s.mux.Use(middleware.MeasureHTTPResponseTime) + jimmParameters := jimm.Parameters{ + UUID: p.ControllerUUID, + Pubsub: &pubsub.Hub{MaxConcurrency: 50}, + } // Setup all dependency services - - if p.ControllerUUID == "" { - controllerUUID, err := uuid.NewRandom() - if err != nil { - return nil, errors.E(op, err) - } - p.ControllerUUID = controllerUUID.String() + if jimmParameters.UUID == "" { + jimmParameters.UUID = uuid.NewString() } - s.jimm.UUID = p.ControllerUUID - s.jimm.Pubsub = &pubsub.Hub{MaxConcurrency: 50} if p.DSN == "" { return nil, errors.E(op, "missing DSN") } - var err error - s.jimm.Database.DB, err = openDB(ctx, p.DSN, p.LogSQL) + database, err := openDB(ctx, p.DSN, p.LogSQL) if err != nil { return nil, errors.E(op, err) } - if err := s.jimm.Database.Migrate(ctx, false); err != nil { - return nil, errors.E(op, err) - } - - if p.AuditLogRetentionPeriodInDays != "" { - period, err := strconv.Atoi(p.AuditLogRetentionPeriodInDays) - if err != nil { - return nil, errors.E(op, "failed to parse audit log retention period") - } - if period < 0 { - return nil, errors.E(op, "retention period cannot be less than 0") - } - if period != 0 { - jimm.NewAuditLogCleanupService(s.jimm.Database, period).Start(ctx) - } + db := &db.Database{ + DB: database, } + jimmParameters.Database = db openFGAclient, err := newOpenFGAClient(ctx, p.OpenFGAParams) if err != nil { return nil, errors.E(op, err) } - s.jimm.OpenFGAClient = openFGAclient + jimmParameters.OpenFGAClient = openFGAclient + if err := ensureControllerAdministrators(ctx, openFGAclient, p.ControllerUUID, p.ControllerAdmins); err != nil { return nil, errors.E(op, err, "failed to ensure controller admins") } - if err := s.setupCredentialStore(ctx, p); err != nil { + + credentialStore, err := s.setupCredentialStore(ctx, p, db) + if err != nil { return nil, errors.E(op, err) } + jimmParameters.CredentialStore = credentialStore - sessionStore, err := s.setupSessionStore(ctx, p.CookieSessionKey) + sessionStore, err := s.setupSessionStore(ctx, p.CookieSessionKey, db) if err != nil { return nil, errors.E(op, err) } @@ -374,12 +362,12 @@ func NewService(ctx context.Context, p Params) (*Service, error) { SessionCookieMaxAge: p.OAuthAuthenticatorParams.SessionCookieMaxAge, JWTSessionKey: p.OAuthAuthenticatorParams.JWTSessionKey, SecureCookies: p.OAuthAuthenticatorParams.SecureSessionCookies, - Store: &s.jimm.Database, + Store: db, SessionStore: sessionStore, RedirectURL: redirectUrl, }, ) - s.jimm.OAuthAuthenticator = authSvc + jimmParameters.OAuthAuthenticator = authSvc if err != nil { zapctx.Error(ctx, "failed to setup authentication service", zap.Error(err)) return nil, errors.E(op, err, "failed to setup authentication service") @@ -389,42 +377,36 @@ func NewService(ctx context.Context, p Params) (*Service, error) { p.JWTExpiryDuration = 24 * time.Hour } - s.jimm.JWKService = jimmjwx.NewJWKSService(s.jimm.CredentialStore) - s.jimm.JWTService = jimmjwx.NewJWTService(jimmjwx.JWTServiceParams{ + jimmParameters.JWKService = jimmjwx.NewJWKSService(credentialStore) + jimmParameters.JWTService = jimmjwx.NewJWTService(jimmjwx.JWTServiceParams{ Host: p.PublicDNSName, - Store: s.jimm.CredentialStore, + Store: credentialStore, Expiry: p.JWTExpiryDuration, }) - s.jimm.Dialer = &jujuclient.Dialer{ - ControllerCredentialsStore: s.jimm.CredentialStore, - JWTService: s.jimm.JWTService, + jimmParameters.Dialer = &jujuclient.Dialer{ + ControllerCredentialsStore: credentialStore, + JWTService: jimmParameters.JWTService, } - roleManager, err := role.NewRoleManager(&s.jimm.Database, s.jimm.OpenFGAClient) - if err != nil { - return nil, errors.E(op, err, "failed to create RoleManager") - } - s.jimm.RoleManager = roleManager - - groupManager, err := group.NewGroupManager(&s.jimm.Database, s.jimm.OpenFGAClient) - if err != nil { - return nil, errors.E(op, err, "failed to create GroupManager") - } - s.jimm.GroupManager = groupManager - if !p.DisableConnectionCache { - s.jimm.Dialer = jimm.CacheDialer(s.jimm.Dialer) + jimmParameters.Dialer = jimm.CacheDialer(jimmParameters.Dialer) } if _, err := url.Parse(p.DashboardFinalRedirectURL); err != nil { return nil, errors.E(op, err, "failed to parse final redirect url for the dashboard") } - rebacBackend, err := rebac_admin.SetupBackend(ctx, &s.jimm) + // instantiate jimm + s.jimm, err = jimm.New(jimmParameters) if err != nil { return nil, errors.E(op, err) } + s.mux = chi.NewRouter() + + s.mux.Use(chimiddleware.RequestLogger(&logger.HTTPLogFormatter{})) + s.mux.Use(middleware.MeasureHTTPResponseTime) + // Setup CORS middleware corsOpts := cors.New(cors.Options{ AllowedOrigins: p.CorsAllowedOrigins, @@ -440,7 +422,12 @@ func NewService(ctx context.Context, p Params) (*Service, error) { s.mux.Mount("/metrics", promhttp.Handler()) - s.mux.Mount("/rebac", middleware.AuthenticateRebac("/rebac", rebacBackend.Handler(""), &s.jimm)) + rebacBackend, err := rebac_admin.SetupBackend(ctx, s.jimm) + if err != nil { + return nil, errors.E(op, err) + } + + s.mux.Mount("/rebac", middleware.AuthenticateRebac("/rebac", rebacBackend.Handler(""), s.jimm)) mountHandler( "/debug", @@ -486,16 +473,69 @@ func NewService(ctx context.Context, p Params) (*Service, error) { // Websockets require extra care when cookies are used for authentication // to avoid CSRF attacks. https://portswigger.net/web-security/websockets/cross-site-websocket-hijacking websocketCors := middleware.NewWebsocketCors(p.CorsAllowedOrigins) - s.mux.Handle("/api", websocketCors.Handler(jujuapi.APIHandler(ctx, &s.jimm, params))) - s.mux.Handle("/model/*", websocketCors.Handler(http.StripPrefix("/model", jujuapi.ModelHandler(ctx, &s.jimm, params)))) + s.mux.Handle("/api", websocketCors.Handler(jujuapi.APIHandler(ctx, s.jimm, params))) + s.mux.Handle("/model/*", websocketCors.Handler(http.StripPrefix("/model", jujuapi.ModelHandler(ctx, s.jimm, params)))) mountHandler( "/model/{uuid}/{type:charms|applications}", - jimmhttp.NewHTTPProxyHandler(&s.jimm), + jimmhttp.NewHTTPProxyHandler(s.jimm), ) + if p.AuditLogRetentionPeriodInDays != "" { + var err error + s.auditLogCleanupPeriod, err = strconv.Atoi(p.AuditLogRetentionPeriodInDays) + if err != nil { + return nil, errors.E(op, "failed to parse audit log retention period") + } + if s.auditLogCleanupPeriod < 0 { + return nil, errors.E(op, "retention period cannot be less than 0") + } + } + s.isLeader = p.IsLeader + return s, nil } +func (s *Service) StartServices(ctx context.Context, svc *service.Service) { + // on the leader unit we start additional routines + if s.isLeader { + // the leader unit connects to all controllers' AllWatcher + svc.Go(func() error { + return s.WatchControllers(ctx) + }) + + // audit log cleanup routine + if s.auditLogCleanupPeriod != 0 { + svc.Go(func() error { + jimm.NewAuditLogCleanupService(s.jimm.Database, s.auditLogCleanupPeriod).Start(ctx) + return nil + }) + } + + // the JWKS rotator + svc.Go(func() error { + if err := s.StartJWKSRotator(ctx, time.NewTicker(time.Hour).C, time.Now().UTC().AddDate(0, 3, 0)); err != nil { + zapctx.Error(ctx, "failed to start JWKS rotator", zap.Error(err)) + return err + } + return nil + }) + + // OpenFGA cleanup - cleans up all orphaned tuples + svc.Go(func() error { + return s.OpenFGACleanup(ctx, time.NewTicker(6*time.Hour).C) + }) + } + + // all units periodically update their controller/model metrics + svc.Go(func() error { + s.MonitorResources(ctx) + return nil + }) + + // all units watch for model summaries + svc.Go(func() error { return s.WatchModelSummaries(ctx) }) +} + // setupDischarger set JIMM up as a discharger of 3rd party caveats addressed to it. This is intended // to enable Juju controllers to check for permissions using a macaroon-based workflow (atm only // for cross model relations). @@ -506,21 +546,17 @@ func (s *Service) setupDischarger(p Params) (*discharger.MacaroonDischarger, err MacaroonExpiryDuration: p.MacaroonExpiryDuration, ControllerUUID: p.ControllerUUID, } - MacaroonDischarger, err := discharger.NewMacaroonDischarger(cfg, &s.jimm.Database, s.jimm.OpenFGAClient) + MacaroonDischarger, err := discharger.NewMacaroonDischarger(cfg, s.jimm.Database, s.jimm.OpenFGAClient) if err != nil { return nil, errors.E(err) } return MacaroonDischarger, nil } -func (s *Service) setupSessionStore(ctx context.Context, sessionSecret []byte) (*pgstore.PGStore, error) { +func (s *Service) setupSessionStore(ctx context.Context, sessionSecret []byte, db *db.Database) (*pgstore.PGStore, error) { const op = errors.Op("setupSessionStore") - if s.jimm.CredentialStore == nil { - return nil, errors.E(op, "credential store is not configured") - } - - sqlDb, err := s.jimm.Database.DB.DB() + sqlDb, err := db.DB.DB() if err != nil { return nil, errors.E(op, err) } @@ -561,27 +597,25 @@ func openDB(ctx context.Context, dsn string, logSQL bool) (*gorm.DB, error) { }) } -func (s *Service) setupCredentialStore(ctx context.Context, p Params) error { +func (s *Service) setupCredentialStore(ctx context.Context, p Params, db *db.Database) (jimmcreds.CredentialStore, error) { const op = errors.Op("newSecretStore") // Only enable Postgres storage for secrets if explicitly enabled. if p.InsecureSecretStorage { zapctx.Warn(ctx, "using plaintext postgres for secret storage") - s.jimm.CredentialStore = &s.jimm.Database - return nil + return db, nil } vs, err := newVaultStore(ctx, p) if err != nil { zapctx.Error(ctx, "Vault Store error", zap.Error(err)) - return errors.E(op, err) + return nil, errors.E(op, err) } if vs != nil { - s.jimm.CredentialStore = vs - return nil + return vs, nil } - return errors.E(op, "jimm cannot start without a credential store") + return nil, errors.E(op, "jimm cannot start without a credential store") } func newVaultStore(ctx context.Context, p Params) (jimmcreds.CredentialStore, error) { diff --git a/cmd/jimmsrv/service/service_test.go b/cmd/jimmsrv/service/service_test.go index 5498033b7..f8f625ec3 100644 --- a/cmd/jimmsrv/service/service_test.go +++ b/cmd/jimmsrv/service/service_test.go @@ -38,10 +38,10 @@ func TestMain(m *testing.M) { os.Exit(code) } -// newTestJimmParams returns a set of JIMM params with sensible defaults +// newTestServiceParameters returns a set of JIMM params with sensible defaults // for tests. A test can override any parameter that it needs. -// Note that newTestJimmParams will create an empty test database. -func newTestJimmParams(t jimmtest.Tester) jimmsvc.Params { +// Note that newTestServiceParameters will create an empty test database. +func newTestServiceParameters(t jimmtest.Tester) jimmsvc.Params { return jimmsvc.Params{ DSN: jimmtest.CreateEmptyDatabase(t), ControllerUUID: "6acf4fd8-32d6-49ea-b4eb-dcb9d1590c11", @@ -62,12 +62,14 @@ func newTestJimmParams(t jimmtest.Tester) jimmsvc.Params { func TestDefaultService(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) p.InsecureSecretStorage = true - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() rr := httptest.NewRecorder() @@ -82,24 +84,28 @@ func TestDefaultService(t *testing.T) { func TestServiceDoesNotStartWithoutCredentialStore(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) - _, err = jimmsvc.NewService(context.Background(), p) + _, err = jimmsvc.NewService(ctx, p) c.Assert(err, qt.ErrorMatches, "jimm cannot start without a credential store") } func TestAuthenticator(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.InsecureSecretStorage = true p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() @@ -149,13 +155,14 @@ const testVaultEnv = `clouds: func TestVault(t *testing.T) { c := qt.New(t) + ctx := context.Background() ofgaClient, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) vaultClient, _, roleID, roleSecretID, _ := jimmtest.VaultClient(c) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.VaultAddress = "http://localhost:8200" p.VaultPath = "/jimm-kv/" p.VaultRoleID = roleID @@ -213,25 +220,28 @@ func TestVault(t *testing.T) { func TestPostgresSecretStore(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.InsecureSecretStorage = true p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() } func TestOpenFGA(t *testing.T) { c := qt.New(t) + ctx := context.Background() _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.InsecureSecretStorage = true p.ControllerAdmins = []string{"alice", "eve"} p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) @@ -276,13 +286,15 @@ func TestOpenFGA(t *testing.T) { func TestPublicKey(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) p.InsecureSecretStorage = true - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() @@ -300,14 +312,16 @@ func TestPublicKey(t *testing.T) { func TestRebacAdminApi(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.InsecureSecretStorage = true p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() @@ -326,6 +340,8 @@ func TestRebacAdminApi(t *testing.T) { func TestThirdPartyCaveatDischarge(t *testing.T) { c := qt.New(t) + ctx := context.Background() + offer := dbmodel.ApplicationOffer{ UUID: "7e4e7ffb-5116-4544-a400-f584d08c410e", Name: "test-application-offer", @@ -333,8 +349,6 @@ func TestThirdPartyCaveatDischarge(t *testing.T) { user, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) - ctx := context.Background() - tests := []struct { about string setup func(c *qt.C, ofgaClient *openfga.OFGAClient, user *dbmodel.Identity) @@ -382,10 +396,10 @@ func TestThirdPartyCaveatDischarge(t *testing.T) { ofgaClient, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) p.InsecureSecretStorage = true - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() @@ -447,14 +461,16 @@ func TestThirdPartyCaveatDischarge(t *testing.T) { func TestDisableOAuthEndpointsWhenDashboardRedirectURLNotSet(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.DashboardFinalRedirectURL = "" p.InsecureSecretStorage = true p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() @@ -470,15 +486,17 @@ func TestDisableOAuthEndpointsWhenDashboardRedirectURLNotSet(t *testing.T) { func TestEnableOAuthEndpointsWhenDashboardRedirectURLSet(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.DashboardFinalRedirectURL = "some-redirect-url" p.InsecureSecretStorage = true p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() @@ -519,13 +537,15 @@ func TestCleanup(t *testing.T) { func TestCleanupDoesNotPanic_SessionStoreRelatedCleanups(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) p.InsecureSecretStorage = true - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) // Make sure `cleanups` is not empty. @@ -537,15 +557,17 @@ func TestCleanupDoesNotPanic_SessionStoreRelatedCleanups(t *testing.T) { func TestCORS(t *testing.T) { c := qt.New(t) + ctx := context.Background() + _, _, cofgaParams, err := jimmtest.SetupTestOFGAClient(c.Name()) c.Assert(err, qt.IsNil) - p := newTestJimmParams(c) + p := newTestServiceParameters(c) p.OpenFGAParams = cofgaParamsToJIMMOpenFGAParams(*cofgaParams) allowedOrigin := "http://my-referrer.com" p.CorsAllowedOrigins = []string{allowedOrigin} p.InsecureSecretStorage = true - svc, err := jimmsvc.NewService(context.Background(), p) + svc, err := jimmsvc.NewService(ctx, p) c.Assert(err, qt.IsNil) defer svc.Cleanup() diff --git a/docker-compose.yaml b/docker-compose.yaml index 3bddbcabe..9a43dbf46 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -70,7 +70,7 @@ services: POSTGRES_PASSWORD: jimm # Since it's mainly used for testing purposes, it's okay to set fsync=off for # improved performance. - command: -c fsync=off -c full_page_writes=off + command: -c fsync=off -c full_page_writes=off -c max_connections=200 healthcheck: test: [ "CMD-SHELL", "pg_isready -U jimm" ] interval: 5s diff --git a/internal/db/cloud_test.go b/internal/db/cloud_test.go index 56ba0265b..5f935d869 100644 --- a/internal/db/cloud_test.go +++ b/internal/db/cloud_test.go @@ -244,7 +244,7 @@ controllers: region: test-region priority: 1 `) - env.PopulateDB(c, *s.Database) + env.PopulateDB(c, s.Database) cr, err := s.Database.FindRegion(ctx, "testp", "test-region") c.Assert(err, qt.IsNil) @@ -363,7 +363,7 @@ controllers: region: test-region-2 priority: 1 `) - env.PopulateDB(c, *s.Database) + env.PopulateDB(c, s.Database) cl := dbmodel.Cloud{ Name: "test-cloud-1", diff --git a/internal/db/cloudcredential_test.go b/internal/db/cloudcredential_test.go index b0d023619..ae1d541a1 100644 --- a/internal/db/cloudcredential_test.go +++ b/internal/db/cloudcredential_test.go @@ -278,7 +278,7 @@ func (s *dbSuite) TestForEachCloudCredential(c *qt.C) { env := jimmtest.ParseEnvironment(c, forEachCloudCredentialEnv) err := s.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, *s.Database) + env.PopulateDB(c, s.Database) for _, test := range forEachCloudCredentialTests { c.Run(test.name, func(c *qt.C) { diff --git a/internal/db/controller_test.go b/internal/db/controller_test.go index 7241f4b3f..2f4797da5 100644 --- a/internal/db/controller_test.go +++ b/internal/db/controller_test.go @@ -138,7 +138,7 @@ func (s *dbSuite) TestForEachController(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testForEachControllerEnv) - env.PopulateDB(c, *s.Database) + env.PopulateDB(c, s.Database) testError := errors.E("test error") err = s.Database.ForEachController(ctx, func(controller *dbmodel.Controller) error { @@ -321,9 +321,9 @@ func (s *dbSuite) TestForEachControllerModel(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testForEachControllerModelEnv) - env.PopulateDB(c, *s.Database) + env.PopulateDB(c, s.Database) - ctl := env.Controller("test").DBObject(c, *s.Database) + ctl := env.Controller("test").DBObject(c, s.Database) testError := errors.E("test error") err = s.Database.ForEachControllerModel(ctx, &ctl, func(_ *dbmodel.Model) error { return testError diff --git a/internal/db/identitymodeldefaults_test.go b/internal/db/identitymodeldefaults_test.go index 654690ff4..fe1f74149 100644 --- a/internal/db/identitymodeldefaults_test.go +++ b/internal/db/identitymodeldefaults_test.go @@ -5,7 +5,6 @@ package db_test import ( "context" "testing" - "time" qt "github.com/frankban/quicktest" "github.com/google/go-cmp/cmp/cmpopts" @@ -21,7 +20,6 @@ func TestSetIdentityModelDefaults(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now() type testConfig struct { identity *dbmodel.Identity @@ -135,17 +133,11 @@ func TestSetIdentityModelDefaults(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - j := &jimm.JIMM{ - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err := j.Database.Migrate(ctx, true) - c.Assert(err, qt.Equals, nil) + j := jimmtest.NewJIMM(c, nil) testConfig := test.setup(c, j) - err = j.SetIdentityModelDefaults(ctx, testConfig.identity, testConfig.defaults) + err := j.SetIdentityModelDefaults(ctx, testConfig.identity, testConfig.defaults) if testConfig.expectedError == "" { c.Assert(err, qt.Equals, nil) dbDefaults := dbmodel.IdentityModelDefaults{ diff --git a/internal/db/model_test.go b/internal/db/model_test.go index cc48fa4bb..cef4c0a68 100644 --- a/internal/db/model_test.go +++ b/internal/db/model_test.go @@ -533,7 +533,7 @@ func (s *dbSuite) TestForEachModel(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testForEachModelEnv) - env.PopulateDB(c, *s.Database) + env.PopulateDB(c, s.Database) testError := errors.E("test error") err = s.Database.ForEachModel(ctx, func(m *dbmodel.Model) error { @@ -619,7 +619,7 @@ func (s *dbSuite) TestGetModelsByUUID(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testGetModelsByUUIDEnv) - env.PopulateDB(c, *s.Database) + env.PopulateDB(c, s.Database) modelUUIDs := []string{ "00000002-0000-0000-0000-000000000001", @@ -782,9 +782,9 @@ func (s *dbSuite) TestCountModelsByController(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testCountModelsByControllerEnv) - env.PopulateDB(c, *s.Database) + env.PopulateDB(c, s.Database) c.Assert(len(env.Controllers), qt.Equals, 1) - count, err := s.Database.CountModelsByController(context.Background(), env.Controllers[0].DBObject(c, *s.Database)) + count, err := s.Database.CountModelsByController(context.Background(), env.Controllers[0].DBObject(c, s.Database)) c.Assert(err, qt.IsNil) c.Assert(count, qt.Equals, 3) } diff --git a/internal/jimm/access.go b/internal/jimm/access.go index e7e84c588..21a91c407 100644 --- a/internal/jimm/access.go +++ b/internal/jimm/access.go @@ -707,7 +707,7 @@ func (j *JIMM) parseAndValidateTag(ctx context.Context, key string) (*ofganames. return tag, nil } tagString := key - tag, err := resolveTag(j.UUID, &j.Database, tagString) + tag, err := resolveTag(j.UUID, j.Database, tagString) if err != nil { zapctx.Debug(ctx, "failed to resolve tuple object", zap.Error(err)) return nil, errors.E(op, errors.CodeFailedToResolveTupleResource, err) diff --git a/internal/jimm/access_test.go b/internal/jimm/access_test.go index 5d3edde2f..5efbfba74 100644 --- a/internal/jimm/access_test.go +++ b/internal/jimm/access_test.go @@ -6,7 +6,6 @@ import ( "context" "fmt" "testing" - "time" "github.com/canonical/ofga" petname "github.com/dustinkirkland/golang-petname" @@ -14,7 +13,6 @@ import ( "github.com/google/uuid" "github.com/juju/names/v5" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm" @@ -111,21 +109,10 @@ func (t *testJWTService) NewJWT(ctx context.Context, params jimmjwx.JWTParams) ( func TestAuditLogAccess(t *testing.T) { c := qt.New(t) - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) i, err := dbmodel.NewIdentity("alice") c.Assert(err, qt.IsNil) adminUser := openfga.NewUser(i, j.OpenFGAClient) @@ -428,20 +415,7 @@ func TestParseAndValidateTag(t *testing.T) { c := qt.New(t) ctx := context.Background() - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) user, _, _, model, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) @@ -479,16 +453,7 @@ func TestResolveTags(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) identity, group, controller, model, offer, cloud, _, role := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) @@ -556,7 +521,7 @@ func TestResolveTags(t *testing.T) { for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { - jujuTag, err := jimm.ResolveTag(j.UUID, &j.Database, tC.input) + jujuTag, err := jimm.ResolveTag(j.UUID, j.Database, tC.input) c.Assert(err, qt.IsNil) c.Assert(jujuTag, qt.DeepEquals, tC.expected) }) @@ -567,16 +532,7 @@ func TestResolveTupleObjectHandlesErrors(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) _, _, controller, model, offer, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) @@ -627,7 +583,7 @@ func TestResolveTupleObjectHandlesErrors(t *testing.T) { } for i, tc := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { - _, err := jimm.ResolveTag(j.UUID, &j.Database, tc.input) + _, err := jimm.ResolveTag(j.UUID, j.Database, tc.input) c.Assert(err, qt.ErrorMatches, tc.want) }) } @@ -637,16 +593,7 @@ func TestToJAASTag(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) user, group, controller, model, applicationOffer, cloud, _, role := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) @@ -699,16 +646,7 @@ func TestToJAASTagNoUUIDResolution(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) user, group, controller, model, applicationOffer, cloud, _, role := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) serviceAccountId := petname.Generate(2, "-") + "@serviceaccount" @@ -760,23 +698,10 @@ func TestOpenFGACleanup(t *testing.T) { c := qt.New(t) ctx := context.Background() - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) // run cleanup on an empty authorizaton store - err = j.OpenFGACleanup(ctx) + err := j.OpenFGACleanup(ctx) c.Assert(err, qt.IsNil) type createTagFunction func(int) *ofga.Entity @@ -838,7 +763,7 @@ func TestOpenFGACleanup(t *testing.T) { Relation: ofga.Relation(test.relation), Target: targetTag, } - err = ofgaClient.AddRelation(ctx, tuple) + err = j.OpenFGAClient.AddRelation(ctx, tuple) c.Assert(err, qt.IsNil) orphanedTuples = append(orphanedTuples, tuple) @@ -850,7 +775,7 @@ func TestOpenFGACleanup(t *testing.T) { for _, tuple := range orphanedTuples { c.Logf("checking relation for %+v", tuple) - ok, err := ofgaClient.CheckRelation(ctx, tuple, false) + ok, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(ok, qt.IsFalse) } diff --git a/internal/jimm/admin_test.go b/internal/jimm/admin_test.go index 9745504d7..7dc405366 100644 --- a/internal/jimm/admin_test.go +++ b/internal/jimm/admin_test.go @@ -13,18 +13,16 @@ import ( "github.com/lestrrat-go/jwx/v2/jwt" "golang.org/x/oauth2" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/jimm" "github.com/canonical/jimm/v3/internal/testutils/jimmtest" ) func TestLoginDevice(t *testing.T) { c := qt.New(t) - mockAuthenticator := jimmtest.NewMockOAuthAuthenticator(c, nil) - jimm := jimm.JIMM{ - OAuthAuthenticator: &mockAuthenticator, - } - resp, err := jimm.LoginDevice(context.Background()) + + j := jimmtest.NewJIMM(c, nil) + + resp, err := j.LoginDevice(context.Background()) c.Assert(err, qt.IsNil) c.Assert(*resp, qt.CmpEquals(cmpopts.IgnoreTypes(time.Time{})), oauth2.DeviceAuthResponse{ DeviceCode: "test-device-code", @@ -38,12 +36,15 @@ func TestLoginDevice(t *testing.T) { func TestGetDeviceSessionToken(t *testing.T) { c := qt.New(t) pollingChan := make(chan string, 1) + mockAuthenticator := jimmtest.NewMockOAuthAuthenticator(c, pollingChan) - jimm := jimm.JIMM{ + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ OAuthAuthenticator: &mockAuthenticator, - } + }) + pollingChan <- "user-foo" - token, err := jimm.GetDeviceSessionToken(context.Background(), nil) + token, err := j.GetDeviceSessionToken(context.Background(), nil) c.Assert(err, qt.IsNil) c.Assert(token, qt.Not(qt.Equals), "") decodedToken, err := base64.StdEncoding.DecodeString(token) @@ -55,46 +56,26 @@ func TestGetDeviceSessionToken(t *testing.T) { func TestLoginClientCredentials(t *testing.T) { c := qt.New(t) - mockAuthenticator := jimmtest.NewMockOAuthAuthenticator(c, nil) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), t.Name()) - c.Assert(err, qt.IsNil) - jimm := jimm.JIMM{ - UUID: "foo", - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OAuthAuthenticator: &mockAuthenticator, - OpenFGAClient: client, - } + + j := jimmtest.NewJIMM(c, nil) + ctx := context.Background() - err = jimm.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) invalidClientID := "123@123@" - _, err = jimm.LoginClientCredentials(ctx, invalidClientID, "foo-secret") + _, err := j.LoginClientCredentials(ctx, invalidClientID, "foo-secret") c.Assert(err, qt.ErrorMatches, "invalid client ID") validClientID := "my-svc-acc" - user, err := jimm.LoginClientCredentials(ctx, validClientID, "foo-secret") + user, err := j.LoginClientCredentials(ctx, validClientID, "foo-secret") c.Assert(err, qt.IsNil) c.Assert(user.Name, qt.Equals, "my-svc-acc@serviceaccount") } func TestLoginWithSessionToken(t *testing.T) { c := qt.New(t) - mockAuthenticator := jimmtest.NewMockOAuthAuthenticator(c, nil) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), t.Name()) - c.Assert(err, qt.IsNil) - jimm := jimm.JIMM{ - UUID: "foo", - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OAuthAuthenticator: &mockAuthenticator, - OpenFGAClient: client, - } + + j := jimmtest.NewJIMM(c, nil) + ctx := context.Background() - err = jimm.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) token, err := jwt.NewBuilder(). Subject("alice@canonical.com"). @@ -104,35 +85,24 @@ func TestLoginWithSessionToken(t *testing.T) { c.Assert(err, qt.IsNil) b64Token := base64.StdEncoding.EncodeToString(serialisedToken) - _, err = jimm.LoginWithSessionToken(ctx, "invalid-token") + _, err = j.LoginWithSessionToken(ctx, "invalid-token") c.Assert(err, qt.ErrorMatches, "failed to decode token") - user, err := jimm.LoginWithSessionToken(ctx, b64Token) + user, err := j.LoginWithSessionToken(ctx, b64Token) c.Assert(err, qt.IsNil) c.Assert(user.Name, qt.Equals, "alice@canonical.com") } func TestLoginWithSessionCookie(t *testing.T) { c := qt.New(t) - mockAuthenticator := jimmtest.NewMockOAuthAuthenticator(c, nil) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), t.Name()) - c.Assert(err, qt.IsNil) - jimm := jimm.JIMM{ - UUID: "foo", - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OAuthAuthenticator: &mockAuthenticator, - OpenFGAClient: client, - } ctx := context.Background() - err = jimm.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - _, err = jimm.LoginWithSessionCookie(ctx, "") + j := jimmtest.NewJIMM(c, nil) + + _, err := j.LoginWithSessionCookie(ctx, "") c.Assert(err, qt.ErrorMatches, "missing cookie identity") - user, err := jimm.LoginWithSessionCookie(ctx, "alice@canonical.com") + user, err := j.LoginWithSessionCookie(ctx, "alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(user.Name, qt.Equals, "alice@canonical.com") } diff --git a/internal/jimm/applicationoffer_test.go b/internal/jimm/applicationoffer_test.go index d079a38c7..39b2be394 100644 --- a/internal/jimm/applicationoffer_test.go +++ b/internal/jimm/applicationoffer_test.go @@ -183,7 +183,6 @@ func TestRevokeOfferAccess(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) tests := []struct { about string @@ -328,31 +327,18 @@ func TestRevokeOfferAccess(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - - db := db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - } - err := db.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.about) - c.Assert(err, qt.IsNil) - jimmUUID := uuid.NewString() - environment := initializeEnvironment(c, ctx, &db, client, jimmUUID) - - j := &jimm.JIMM{ - UUID: jimmUUID, - OpenFGAClient: client, - Database: db, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: &jimmtest.API{}, }, - } + }) + + environment := initializeEnvironment(c, ctx, j.Database, j.OpenFGAClient, jimmUUID) if test.setup != nil { - test.setup(environment, client) + test.setup(environment, j.OpenFGAClient) } authenticatedUser, offerUser, offerURL, revokeAccessLevel := test.parameterFunc(environment) @@ -360,13 +346,13 @@ func TestRevokeOfferAccess(t *testing.T) { offer := dbmodel.ApplicationOffer{ URL: offerURL, } - err := db.GetApplicationOffer(ctx, &offer) + err := j.Database.GetApplicationOffer(ctx, &offer) c.Assert(err, qt.IsNil) - appliedRelation := openfga.NewUser(&offerUser, client).GetApplicationOfferAccess(ctx, offer.ResourceTag()) + appliedRelation := openfga.NewUser(&offerUser, j.OpenFGAClient).GetApplicationOfferAccess(ctx, offer.ResourceTag()) c.Assert(jimm.ToOfferAccessString(appliedRelation), qt.Equals, expectedAppliedRelation) } - err = j.RevokeOfferAccess(ctx, openfga.NewUser(&authenticatedUser, client), offerURL, offerUser.ResourceTag(), revokeAccessLevel) + err := j.RevokeOfferAccess(ctx, openfga.NewUser(&authenticatedUser, j.OpenFGAClient), offerURL, offerUser.ResourceTag(), revokeAccessLevel) if test.expectedError == "" { c.Assert(err, qt.IsNil) assertAppliedRelation(test.expectedAccessLevel) @@ -384,7 +370,6 @@ func TestGrantOfferAccess(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) tests := []struct { about string @@ -497,40 +482,29 @@ func TestGrantOfferAccess(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - - db := db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - } - err := db.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.about) - c.Assert(err, qt.IsNil) - jimmUUID := uuid.NewString() - environment := initializeEnvironment(c, ctx, &db, client, jimmUUID) - authenticatedUser, offerUser, offerURL, grantAccessLevel := test.parameterFunc(environment) + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + UUID: jimmUUID, - j := &jimm.JIMM{ - UUID: jimmUUID, - OpenFGAClient: client, - Database: db, Dialer: &jimmtest.Dialer{ API: &jimmtest.API{}, }, - } + }) + + environment := initializeEnvironment(c, ctx, j.Database, j.OpenFGAClient, jimmUUID) + authenticatedUser, offerUser, offerURL, grantAccessLevel := test.parameterFunc(environment) - err = j.GrantOfferAccess(ctx, openfga.NewUser(&authenticatedUser, client), offerURL, offerUser.ResourceTag(), grantAccessLevel) + err := j.GrantOfferAccess(ctx, openfga.NewUser(&authenticatedUser, j.OpenFGAClient), offerURL, offerUser.ResourceTag(), grantAccessLevel) if test.expectedError == "" { c.Assert(err, qt.IsNil) offer := dbmodel.ApplicationOffer{ URL: offerURL, } - err = db.GetApplicationOffer(ctx, &offer) + err = j.Database.GetApplicationOffer(ctx, &offer) c.Assert(err, qt.IsNil) - appliedRelation := openfga.NewUser(&offerUser, client).GetApplicationOfferAccess(ctx, offer.ResourceTag()) + appliedRelation := openfga.NewUser(&offerUser, j.OpenFGAClient).GetApplicationOfferAccess(ctx, offer.ResourceTag()) c.Assert(jimm.ToOfferAccessString(appliedRelation), qt.Equals, test.expectedAccessLevel) } else { c.Assert(err, qt.ErrorMatches, test.expectedError) @@ -548,7 +522,7 @@ func TestGetApplicationOfferConsumeDetails(t *testing.T) { ctx := context.Background() now := time.Now().UTC().Round(time.Millisecond) - db := db.Database{ + db := &db.Database{ DB: jimmtest.PostgresDB(c, func() time.Time { return now }), } err = db.Migrate(ctx, false) @@ -647,8 +621,7 @@ func TestGetApplicationOfferConsumeDetails(t *testing.T) { err = openfga.NewUser(uAll, client).SetApplicationOfferAccess(ctx, offer.ResourceTag(), ofganames.ReaderRelation) c.Assert(err, qt.IsNil) - j := &jimm.JIMM{ - UUID: uuid.NewString(), + j := jimmtest.NewJIMM(c, &jimm.Parameters{ OpenFGAClient: client, Database: db, Dialer: &jimmtest.Dialer{ @@ -685,7 +658,7 @@ func TestGetApplicationOfferConsumeDetails(t *testing.T) { }, }, }, - } + }) tests := []struct { about string @@ -809,17 +782,8 @@ func TestGetApplicationOffer(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: &jimmtest.API{ GetApplicationOffer_: func(_ context.Context, details *jujuparams.ApplicationOfferAdminDetailsV5) error { @@ -857,10 +821,7 @@ func TestGetApplicationOffer(t *testing.T) { }, }, }, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) @@ -930,11 +891,11 @@ func TestGetApplicationOffer(t *testing.T) { c.Assert(err, qt.IsNil) // user u is administrator of the test offer - err = openfga.NewUser(u, client).SetApplicationOfferAccess(ctx, offer.ResourceTag(), ofganames.AdministratorRelation) + err = openfga.NewUser(u, j.OpenFGAClient).SetApplicationOfferAccess(ctx, offer.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) // user u1 is reader of the test offer - err = openfga.NewUser(u1, client).SetApplicationOfferAccess(ctx, offer.ResourceTag(), ofganames.ReaderRelation) + err = openfga.NewUser(u1, j.OpenFGAClient).SetApplicationOfferAccess(ctx, offer.ResourceTag(), ofganames.ReaderRelation) c.Assert(err, qt.IsNil) tests := []struct { @@ -1014,7 +975,7 @@ func TestGetApplicationOffer(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - details, err := j.GetApplicationOffer(ctx, openfga.NewUser(test.user, client), test.offerURL) + details, err := j.GetApplicationOffer(ctx, openfga.NewUser(test.user, j.OpenFGAClient), test.offerURL) if test.expectedError == "" { c.Assert(err, qt.IsNil) sort.Slice(details.Users, func(i, j int) bool { @@ -1031,13 +992,12 @@ func TestGetApplicationOffer(t *testing.T) { func TestOffer(t *testing.T) { c := qt.New(t) - now := time.Now().UTC().Round(time.Millisecond) tests := []struct { about string getApplicationOffer func(context.Context, *jujuparams.ApplicationOfferAdminDetailsV5) error grantApplicationOfferAccess func(context.Context, string, names.UserTag, jujuparams.OfferAccessPermission) error offer func(context.Context, crossmodel.OfferURL, jujuparams.AddApplicationOffer) error - createEnv func(*qt.C, db.Database, *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) + createEnv func(*qt.C, *db.Database, *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) }{{ about: "all ok", getApplicationOffer: func(_ context.Context, details *jujuparams.ApplicationOfferAdminDetailsV5) error { @@ -1073,7 +1033,7 @@ func TestOffer(t *testing.T) { offer: func(context.Context, crossmodel.OfferURL, jujuparams.AddApplicationOffer) error { return nil }, - createEnv: func(c *qt.C, db db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { + createEnv: func(c *qt.C, db *db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { ctx := context.Background() u, err := dbmodel.NewIdentity("alice@canonical.com") @@ -1163,7 +1123,7 @@ func TestOffer(t *testing.T) { offer: func(context.Context, crossmodel.OfferURL, jujuparams.AddApplicationOffer) error { return errors.E("a silly error") }, - createEnv: func(c *qt.C, db db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { + createEnv: func(c *qt.C, db *db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { ctx := context.Background() u, err := dbmodel.NewIdentity("alice@canonical.com") @@ -1250,7 +1210,7 @@ func TestOffer(t *testing.T) { offer: func(context.Context, crossmodel.OfferURL, jujuparams.AddApplicationOffer) error { return nil }, - createEnv: func(c *qt.C, db db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { + createEnv: func(c *qt.C, db *db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) @@ -1282,7 +1242,7 @@ func TestOffer(t *testing.T) { offer: func(context.Context, crossmodel.OfferURL, jujuparams.AddApplicationOffer) error { return errors.E(errors.CodeNotFound, "application test-app") }, - createEnv: func(c *qt.C, db db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { + createEnv: func(c *qt.C, db *db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { ctx := context.Background() u, err := dbmodel.NewIdentity("alice@canonical.com") @@ -1371,7 +1331,7 @@ func TestOffer(t *testing.T) { offer: func(context.Context, crossmodel.OfferURL, jujuparams.AddApplicationOffer) error { return nil }, - createEnv: func(c *qt.C, db db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { + createEnv: func(c *qt.C, db *db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { ctx := context.Background() u, err := dbmodel.NewIdentity("alice@canonical.com") @@ -1462,7 +1422,7 @@ func TestOffer(t *testing.T) { offer: func(context.Context, crossmodel.OfferURL, jujuparams.AddApplicationOffer) error { return nil }, - createEnv: func(c *qt.C, db db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { + createEnv: func(c *qt.C, db *db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { ctx := context.Background() u, err := dbmodel.NewIdentity("alice@canonical.com") @@ -1549,7 +1509,7 @@ func TestOffer(t *testing.T) { offer: func(context.Context, crossmodel.OfferURL, jujuparams.AddApplicationOffer) error { return errors.E("application offer already exists") }, - createEnv: func(c *qt.C, db db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { + createEnv: func(c *qt.C, db *db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { ctx := context.Background() u, err := dbmodel.NewIdentity("alice@canonical.com") @@ -1636,26 +1596,17 @@ func TestOffer(t *testing.T) { Offer_: test.offer, } - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.about) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, }, - OpenFGAClient: client, - } + }) ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - u, offerArgs, expectedOffer, errorAssertion := test.createEnv(c, j.Database, client) + u, offerArgs, expectedOffer, errorAssertion := test.createEnv(c, j.Database, j.OpenFGAClient) - err = j.Offer(context.Background(), openfga.NewUser(&u, client), offerArgs) + err := j.Offer(context.Background(), openfga.NewUser(&u, j.OpenFGAClient), offerArgs) if errorAssertion == nil { c.Assert(err, qt.IsNil) @@ -1676,9 +1627,8 @@ func TestOffer(t *testing.T) { func TestOfferAssertOpenFGARelationsExist(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - createEnv := func(c *qt.C, db db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { + createEnv := func(c *qt.C, db *db.Database, client *openfga.OFGAClient) (dbmodel.Identity, jimm.AddApplicationOfferParams, dbmodel.ApplicationOffer, func(*qt.C, error)) { ctx := context.Background() u, err := dbmodel.NewIdentity("alice@canonical.com") @@ -1794,27 +1744,16 @@ func TestOfferAssertOpenFGARelationsExist(t *testing.T) { }, } - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, UUID: "00000000-0000-0000-0000-0000-0000000000001", }, - OpenFGAClient: client, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) - u, offerArgs, expectedOffer, _ := createEnv(c, j.Database, client) + u, offerArgs, expectedOffer, _ := createEnv(c, j.Database, j.OpenFGAClient) - err = j.Offer(context.Background(), openfga.NewUser(&u, client), offerArgs) + err := j.Offer(context.Background(), openfga.NewUser(&u, j.OpenFGAClient), offerArgs) c.Assert(err, qt.IsNil) offer := dbmodel.ApplicationOffer{ @@ -1825,7 +1764,7 @@ func TestOfferAssertOpenFGARelationsExist(t *testing.T) { c.Assert(offer, qt.CmpEquals(cmpopts.EquateEmpty(), cmpopts.IgnoreTypes(time.Time{}, gorm.Model{}), cmpopts.IgnoreTypes(dbmodel.Model{})), expectedOffer) // check the controller relation was created - exists, err := client.CheckRelation( + exists, err := j.OpenFGAClient.CheckRelation( context.Background(), openfga.Tuple{ Object: ofganames.ConvertTag(offerArgs.ModelTag), @@ -1838,7 +1777,7 @@ func TestOfferAssertOpenFGARelationsExist(t *testing.T) { c.Assert(exists, qt.IsTrue) // check the user has administrator rights on the offer - exists, err = client.CheckRelation( + exists, err = j.OpenFGAClient.CheckRelation( context.Background(), openfga.Tuple{ Object: ofganames.ConvertTag(u.ResourceTag()), @@ -1933,7 +1872,6 @@ func TestDestroyOffer(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) destroyErrorsChannel := make(chan error, 1) @@ -1982,24 +1920,7 @@ func TestDestroyOffer(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - - db := db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - } - err := db.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.about) - c.Assert(err, qt.IsNil) - - jimmUUID := uuid.NewString() - - environment := initializeEnvironment(c, ctx, &db, client, jimmUUID) - authenticatedUser, offerURL := test.parameterFunc(environment) - - j := &jimm.JIMM{ - UUID: jimmUUID, - Database: db, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: &jimmtest.API{ DestroyApplicationOffer_: func(context.Context, string, bool) error { @@ -2012,8 +1933,10 @@ func TestDestroyOffer(t *testing.T) { }, }, }, - OpenFGAClient: client, - } + }) + + environment := initializeEnvironment(c, ctx, j.Database, j.OpenFGAClient, j.UUID) + authenticatedUser, offerURL := test.parameterFunc(environment) if test.destroyError != "" { select { @@ -2021,14 +1944,14 @@ func TestDestroyOffer(t *testing.T) { default: } } - err = j.DestroyOffer(ctx, openfga.NewUser(&authenticatedUser, client), offerURL, true) + err := j.DestroyOffer(ctx, openfga.NewUser(&authenticatedUser, j.OpenFGAClient), offerURL, true) if test.expectedError == "" { c.Assert(err, qt.IsNil) offer := dbmodel.ApplicationOffer{ URL: offerURL, } - err = db.GetApplicationOffer(ctx, &offer) + err = j.Database.GetApplicationOffer(ctx, &offer) c.Assert(errors.ErrorCode(err), qt.Equals, errors.CodeNotFound) } else { c.Assert(err, qt.ErrorMatches, test.expectedError) @@ -2041,7 +1964,6 @@ func TestFindApplicationOffers(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) expectedOffer := jujuparams.ApplicationOfferAdminDetailsV5{ ApplicationOfferDetailsV5: jujuparams.ApplicationOfferDetailsV5{ @@ -2107,23 +2029,7 @@ func TestFindApplicationOffers(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - db := db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - } - err := db.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.about) - c.Assert(err, qt.IsNil) - - jimmUUID := uuid.NewString() - - environment := initializeEnvironment(c, ctx, &db, client, jimmUUID) - user, accessLevel, filters := test.parameterFunc(environment) - - j := &jimm.JIMM{ - UUID: jimmUUID, - Database: db, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: &jimmtest.API{ FindApplicationOffers_: func(ctx context.Context, of []jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) { @@ -2134,10 +2040,12 @@ func TestFindApplicationOffers(t *testing.T) { }, }, }, - OpenFGAClient: client, - } + }) - offers, err := j.FindApplicationOffers(ctx, openfga.NewUser(&user, client), filters...) + environment := initializeEnvironment(c, ctx, j.Database, j.OpenFGAClient, j.UUID) + user, accessLevel, filters := test.parameterFunc(environment) + + offers, err := j.FindApplicationOffers(ctx, openfga.NewUser(&user, j.OpenFGAClient), filters...) if test.expectedError == "" { c.Assert(err, qt.IsNil) if test.expectedOffer != nil { @@ -2288,22 +2196,10 @@ func TestListApplicationOffers(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - db := db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - } - err = db.Migrate(ctx, false) - c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, listApplicationsTestEnv) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: &jimmtest.API{ ListApplicationOffers_: func(_ context.Context, filters []jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) { @@ -2416,8 +2312,9 @@ func TestListApplicationOffers(t *testing.T) { }, }, }, - } - env.PopulateDBAndPermissions(c, j.ResourceTag(), db, client) + }) + + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) tuples := []openfga.Tuple{{ Object: ofganames.ConvertTag(names.NewUserTag("alice@canonical.com")), Relation: ofganames.AdministratorRelation, @@ -2455,11 +2352,11 @@ func TestListApplicationOffers(t *testing.T) { Relation: ofganames.ConsumerRelation, Target: ofganames.ConvertTag(names.NewApplicationOfferTag("00000012-0000-0000-0000-000000000003")), }} - err = client.AddRelation(context.Background(), tuples...) + err := j.OpenFGAClient.AddRelation(context.Background(), tuples...) c.Assert(err, qt.IsNil) - u := env.User("alice@canonical.com").DBObject(c, db) - _, err = j.ListApplicationOffers(ctx, openfga.NewUser(&u, client)) + u := env.User("alice@canonical.com").DBObject(c, j.Database) + _, err = j.ListApplicationOffers(ctx, openfga.NewUser(&u, j.OpenFGAClient)) c.Assert(err, qt.ErrorMatches, `at least one filter must be specified`) filters := []jujuparams.OfferFilter{{ @@ -2469,7 +2366,7 @@ func TestListApplicationOffers(t *testing.T) { ModelName: "model-2", }} - offers, err := j.ListApplicationOffers(ctx, openfga.NewUser(&u, client), filters...) + offers, err := j.ListApplicationOffers(ctx, openfga.NewUser(&u, j.OpenFGAClient), filters...) c.Assert(err, qt.IsNil) for i := range offers { diff --git a/internal/jimm/audit_log.go b/internal/jimm/audit_log.go index 7bd4d5cab..a66deb3c9 100644 --- a/internal/jimm/audit_log.go +++ b/internal/jimm/audit_log.go @@ -130,7 +130,7 @@ func (o recorder) HandleReply(r rpc.Request, header *rpc.Header, body interface{ // on a defined retention period. The retention period is in DAYS. type auditLogCleanupService struct { auditLogRetentionPeriodInDays int - db db.Database + db *db.Database } // pollTimeOfDay holds the time hour, minutes and seconds to poll at. @@ -146,7 +146,7 @@ var pollDuration = pollTimeOfDay{ // NewAuditLogCleanupService returns a service capable of cleaning up audit logs // on a defined retention period. The retention period is in DAYS. -func NewAuditLogCleanupService(db db.Database, auditLogRetentionPeriodInDays int) *auditLogCleanupService { +func NewAuditLogCleanupService(db *db.Database, auditLogRetentionPeriodInDays int) *auditLogCleanupService { return &auditLogCleanupService{ auditLogRetentionPeriodInDays: auditLogRetentionPeriodInDays, db: db, diff --git a/internal/jimm/audit_log_test.go b/internal/jimm/audit_log_test.go index 5872b0a8f..b1bd08083 100644 --- a/internal/jimm/audit_log_test.go +++ b/internal/jimm/audit_log_test.go @@ -22,7 +22,7 @@ func TestAuditLogCleanupServicePurgesLogs(t *testing.T) { ctx := context.Background() now := time.Now().UTC().Round(time.Millisecond) - db := db.Database{ + db := &db.Database{ DB: jimmtest.PostgresDB(c, func() time.Time { return now }), } diff --git a/internal/jimm/cloud.go b/internal/jimm/cloud.go index b485ad23f..1cb61e9ac 100644 --- a/internal/jimm/cloud.go +++ b/internal/jimm/cloud.go @@ -160,7 +160,7 @@ func (j *JIMM) AddCloudToController(ctx context.Context, user *openfga.User, con return errors.E(op, err) } - if err := validateCloudRegion(ctx, &j.Database, user, cloud, controllerName); err != nil { + if err := validateCloudRegion(ctx, j.Database, user, cloud, controllerName); err != nil { return errors.E(op, err) } diff --git a/internal/jimm/cloud_test.go b/internal/jimm/cloud_test.go index 74e391d32..8c317a124 100644 --- a/internal/jimm/cloud_test.go +++ b/internal/jimm/cloud_test.go @@ -5,14 +5,11 @@ package jimm_test import ( "context" "testing" - "time" qt "github.com/frankban/quicktest" - "github.com/google/uuid" jujuparams "github.com/juju/juju/rpc/params" "github.com/juju/names/v5" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm" @@ -24,41 +21,29 @@ import ( func TestGetCloud(t *testing.T) { c := qt.New(t) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) aliceIdentity, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) alice := openfga.NewUser( aliceIdentity, - client, + j.OpenFGAClient, ) bobIdentity, err := dbmodel.NewIdentity("bob@canonical.com") c.Assert(err, qt.IsNil) bob := openfga.NewUser( bobIdentity, - client, + j.OpenFGAClient, ) charlieIdentity, err := dbmodel.NewIdentity("charlie@canonical.com") c.Assert(err, qt.IsNil) charlie := openfga.NewUser( charlieIdentity, - client, + j.OpenFGAClient, ) // daphne is a jimm administrator @@ -66,7 +51,7 @@ func TestGetCloud(t *testing.T) { c.Assert(err, qt.IsNil) daphne := openfga.NewUser( daphneIdentity, - client, + j.OpenFGAClient, ) err = daphne.SetControllerAccess( context.Background(), @@ -81,7 +66,7 @@ func TestGetCloud(t *testing.T) { err = j.Database.AddCloud(ctx, cloud) c.Assert(err, qt.IsNil) - err = client.AddCloudController(context.Background(), cloud.ResourceTag(), j.ResourceTag()) + err = j.OpenFGAClient.AddCloudController(context.Background(), cloud.ResourceTag(), j.ResourceTag()) c.Assert(err, qt.IsNil) err = alice.SetCloudAccess(context.Background(), cloud.ResourceTag(), ofganames.AdministratorRelation) @@ -96,7 +81,7 @@ func TestGetCloud(t *testing.T) { err = j.Database.AddCloud(ctx, cloud2) c.Assert(err, qt.IsNil) - err = client.AddCloudController(context.Background(), cloud2.ResourceTag(), j.ResourceTag()) + err = j.OpenFGAClient.AddCloudController(context.Background(), cloud2.ResourceTag(), j.ResourceTag()) c.Assert(err, qt.IsNil) err = j.EveryoneUser().SetCloudAccess(context.Background(), cloud2.ResourceTag(), ofganames.CanAddModelRelation) @@ -151,49 +136,36 @@ func TestGetCloud(t *testing.T) { func TestForEachCloud(t *testing.T) { c := qt.New(t) - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: "test-jimm-uuid", - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) aliceIdentity, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) alice := openfga.NewUser( aliceIdentity, - client, + j.OpenFGAClient, ) bobIdentity, err := dbmodel.NewIdentity("bob@canonical.com") c.Assert(err, qt.IsNil) bob := openfga.NewUser( bobIdentity, - client, + j.OpenFGAClient, ) charlieIdentity, err := dbmodel.NewIdentity("charlie@canonical.com") c.Assert(err, qt.IsNil) charlie := openfga.NewUser( charlieIdentity, - client, + j.OpenFGAClient, ) daphneIdentity, err := dbmodel.NewIdentity("daphne@canonical.com") c.Assert(err, qt.IsNil) daphne := openfga.NewUser( daphneIdentity, - client, + j.OpenFGAClient, ) daphne.JimmAdmin = true @@ -570,42 +542,31 @@ func TestAddHostedCloud(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - api := &jimmtest.API{ AddCloud_: test.addCloud, GrantCloudAccess_: test.grantCloudAccess, Cloud_: test.cloud_, } - dialer := &jimmtest.Dialer{ Err: test.dialError, API: api, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) // since dialer is set up to dial a controller with UUID set to // jimmtest.DefaultControllerUUID we need to add a controller // relation between that controller and JIMM - err = client.AddController(context.Background(), j.ResourceTag(), names.NewControllerTag(jimmtest.DefaultControllerUUID)) - c.Assert(err, qt.IsNil) - - err = j.Database.Migrate(ctx, false) + err := j.OpenFGAClient.AddController(context.Background(), j.ResourceTag(), names.NewControllerTag(jimmtest.DefaultControllerUUID)) c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, addHostedCloudTestEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) err = j.AddHostedCloud(ctx, user, names.NewCloudTag(test.cloudName), test.cloud, false) c.Assert(dialer.IsClosed(), qt.Equals, true) @@ -850,9 +811,6 @@ func TestAddCloudToController(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - api := &jimmtest.API{ AddCloud_: test.addCloud, GrantCloudAccess_: test.grantCloudAccess, @@ -863,29 +821,22 @@ func TestAddCloudToController(t *testing.T) { Err: test.dialError, API: api, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) // since dialer is set up to dial a controller with UUID set to // jimmtest.DefaultControllerUUID we need to add a controller // relation between that controller and JIMM - err = client.AddController(context.Background(), j.ResourceTag(), names.NewControllerTag(jimmtest.DefaultControllerUUID)) - c.Assert(err, qt.IsNil) - - err = j.Database.Migrate(ctx, false) + err := j.OpenFGAClient.AddController(context.Background(), j.ResourceTag(), names.NewControllerTag(jimmtest.DefaultControllerUUID)) c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, addHostedCloudTestEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) // Note that the force flag has no effect here because the Juju responses are mocked. err = j.AddCloudToController(ctx, user, test.controllerName, names.NewCloudTag(test.cloudName), test.cloud, false) @@ -1024,30 +975,22 @@ func TestGrantCloudAccess(t *testing.T) { c.Run(tt.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), tt.name) - c.Assert(err, qt.IsNil) - env := jimmtest.ParseEnvironment(c, tt.env) dialer := &jimmtest.Dialer{ API: &jimmtest.API{}, Err: tt.dialError, } - j := &jimm.JIMM{ - UUID: jimmtest.ControllerUUID, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) + + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(tt.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.GrantCloudAccess(ctx, user, names.NewCloudTag(tt.cloud), names.NewUserTag(tt.targetUsername), tt.access) + err := j.GrantCloudAccess(ctx, user, names.NewCloudTag(tt.cloud), names.NewUserTag(tt.targetUsername), tt.access) c.Assert(dialer.IsClosed(), qt.Equals, true) if tt.expectError != "" { c.Check(err, qt.ErrorMatches, tt.expectError) @@ -1058,7 +1001,7 @@ func TestGrantCloudAccess(t *testing.T) { } c.Assert(err, qt.IsNil) for _, tuple := range tt.expectRelations { - value, err := client.CheckRelation(ctx, tuple, false) + value, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(value, qt.IsTrue, qt.Commentf("expected the tuple to exist after granting")) } @@ -1325,44 +1268,35 @@ func TestRevokeCloudAccess(t *testing.T) { c.Run(tt.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), tt.name) - c.Assert(err, qt.IsNil) - env := jimmtest.ParseEnvironment(c, tt.env) dialer := &jimmtest.Dialer{ API: &jimmtest.API{}, Err: tt.dialError, } - j := &jimm.JIMM{ - UUID: jimmtest.ControllerUUID, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) + + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) if len(tt.extraInitialTuples) > 0 { - err = client.AddRelation(ctx, tt.extraInitialTuples...) + err := j.OpenFGAClient.AddRelation(ctx, tt.extraInitialTuples...) c.Assert(err, qt.IsNil) } if tt.expectRemovedRelations != nil { for _, tuple := range tt.expectRemovedRelations { - value, err := client.CheckRelation(ctx, tuple, false) + value, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(value, qt.IsTrue, qt.Commentf("expected the tuple to exist before revoking")) } } dbUser := env.User(tt.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.RevokeCloudAccess(ctx, user, names.NewCloudTag(tt.cloud), names.NewUserTag(tt.targetUsername), tt.access) + err := j.RevokeCloudAccess(ctx, user, names.NewCloudTag(tt.cloud), names.NewUserTag(tt.targetUsername), tt.access) c.Assert(dialer.IsClosed(), qt.Equals, true) if tt.expectError != "" { c.Check(err, qt.ErrorMatches, tt.expectError) @@ -1374,14 +1308,14 @@ func TestRevokeCloudAccess(t *testing.T) { c.Assert(err, qt.IsNil) if tt.expectRemovedRelations != nil { for _, tuple := range tt.expectRemovedRelations { - value, err := client.CheckRelation(ctx, tuple, false) + value, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(value, qt.IsFalse, qt.Commentf("expected the tuple to be removed after revoking")) } } if tt.expectRelations != nil { for _, tuple := range tt.expectRelations { - value, err := client.CheckRelation(ctx, tuple, false) + value, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(value, qt.IsTrue, qt.Commentf("expected the tuple to exist after revoking")) } @@ -1487,16 +1421,11 @@ func TestRemoveCloud(t *testing.T) { }, Err: test.dialError, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) dbUser := env.User(test.username).DBObject(c, j.Database) @@ -1715,9 +1644,6 @@ func TestUpdateCloud(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - env := jimmtest.ParseEnvironment(c, test.env) dialer := &jimmtest.Dialer{ API: &jimmtest.API{ @@ -1727,23 +1653,18 @@ func TestUpdateCloud(t *testing.T) { UUID: "00000001-0000-0000-0000-000000000001", AgentVersion: "1", } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) + + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) tag := names.NewCloudTag(test.cloud) - err = j.UpdateCloud(ctx, user, tag, test.update) + err := j.UpdateCloud(ctx, user, tag, test.update) c.Assert(dialer.IsClosed(), qt.Equals, true) if test.expectError != "" { c.Check(err, qt.ErrorMatches, test.expectError) @@ -1911,9 +1832,6 @@ func TestRemoveFromControllerCloud(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - env := jimmtest.ParseEnvironment(c, test.env) dialer := &jimmtest.Dialer{ API: &jimmtest.API{ @@ -1921,22 +1839,17 @@ func TestRemoveFromControllerCloud(t *testing.T) { }, Err: test.dialError, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) + + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.RemoveCloudFromController(ctx, user, test.controllerName, names.NewCloudTag(test.cloud)) + err := j.RemoveCloudFromController(ctx, user, test.controllerName, names.NewCloudTag(test.cloud)) c.Assert(dialer.IsClosed(), qt.Equals, true) if test.expectError != "" { c.Check(err, qt.ErrorMatches, test.expectError) diff --git a/internal/jimm/cloudcredential_test.go b/internal/jimm/cloudcredential_test.go index eab46b4db..48380b8e4 100644 --- a/internal/jimm/cloudcredential_test.go +++ b/internal/jimm/cloudcredential_test.go @@ -11,14 +11,12 @@ import ( "time" qt "github.com/frankban/quicktest" - "github.com/google/uuid" "github.com/juju/juju/core/life" "github.com/juju/juju/core/status" jujuparams "github.com/juju/juju/rpc/params" "github.com/juju/names/v5" "github.com/lestrrat-go/jwx/v2/jwk" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm" @@ -30,7 +28,6 @@ import ( func TestUpdateCloudCredential(t *testing.T) { c := qt.New(t) - now := time.Now().UTC().Round(time.Millisecond) arch := "amd64" mem := uint64(8096) cores := uint64(8) @@ -40,16 +37,16 @@ func TestUpdateCloudCredential(t *testing.T) { checkCredentialErrors []error updateCredentialErrors []error jimmAdmin bool - createEnv func(*qt.C, *jimm.JIMM, *openfga.OFGAClient) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) + createEnv func(*qt.C, *jimm.JIMM) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) }{{ about: "all ok", jimmAdmin: true, - createEnv: func(c *qt.C, j *jimm.JIMM, client *openfga.OFGAClient) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { + createEnv: func(c *qt.C, j *jimm.JIMM) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil) - user := openfga.NewUser(u, client) + user := openfga.NewUser(u, j.OpenFGAClient) err = user.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) @@ -157,13 +154,13 @@ func TestUpdateCloudCredential(t *testing.T) { about: "update credential error returned by controller", jimmAdmin: true, updateCredentialErrors: []error{nil, errors.E("test error")}, - createEnv: func(c *qt.C, j *jimm.JIMM, client *openfga.OFGAClient) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { + createEnv: func(c *qt.C, j *jimm.JIMM) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil) - user := openfga.NewUser(u, client) + user := openfga.NewUser(u, j.OpenFGAClient) err = user.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) @@ -248,13 +245,13 @@ func TestUpdateCloudCredential(t *testing.T) { jimmAdmin: true, checkCredentialErrors: []error{errors.E("test error")}, updateCredentialErrors: []error{nil}, - createEnv: func(c *qt.C, j *jimm.JIMM, client *openfga.OFGAClient) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { + createEnv: func(c *qt.C, j *jimm.JIMM) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil) - user := openfga.NewUser(u, client) + user := openfga.NewUser(u, j.OpenFGAClient) err = user.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) @@ -332,13 +329,13 @@ func TestUpdateCloudCredential(t *testing.T) { }, { about: "user is controller superuser", jimmAdmin: true, - createEnv: func(c *qt.C, j *jimm.JIMM, client *openfga.OFGAClient) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { + createEnv: func(c *qt.C, j *jimm.JIMM) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil) - alice := openfga.NewUser(u, client) + alice := openfga.NewUser(u, j.OpenFGAClient) alice.JimmAdmin = true err = alice.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) @@ -361,7 +358,7 @@ func TestUpdateCloudCredential(t *testing.T) { err = alice.SetCloudAccess(context.Background(), cloud.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) - e := openfga.NewUser(eve, client) + e := openfga.NewUser(eve, j.OpenFGAClient) err = e.SetCloudAccess(context.Background(), cloud.ResourceTag(), ofganames.CanAddModelRelation) c.Assert(err, qt.IsNil) @@ -456,13 +453,13 @@ func TestUpdateCloudCredential(t *testing.T) { about: "skip check, which would return an error", checkCredentialErrors: []error{errors.E("test error")}, jimmAdmin: true, - createEnv: func(c *qt.C, j *jimm.JIMM, client *openfga.OFGAClient) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { + createEnv: func(c *qt.C, j *jimm.JIMM) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil) - user := openfga.NewUser(u, client) + user := openfga.NewUser(u, j.OpenFGAClient) err = user.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) @@ -569,13 +566,13 @@ func TestUpdateCloudCredential(t *testing.T) { }, { about: "skip update", jimmAdmin: true, - createEnv: func(c *qt.C, j *jimm.JIMM, client *openfga.OFGAClient) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { + createEnv: func(c *qt.C, j *jimm.JIMM) (*dbmodel.Identity, jimm.UpdateCloudCredentialArgs, dbmodel.CloudCredential, string) { u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil) - user := openfga.NewUser(u, client) + user := openfga.NewUser(u, j.OpenFGAClient) err = user.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) @@ -775,28 +772,22 @@ func TestUpdateCloudCredential(t *testing.T) { }, } - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - Dialer: &jimmtest.Dialer{ - API: api, + j := jimmtest.NewJIMM( + c, + &jimm.Parameters{ + Dialer: &jimmtest.Dialer{ + API: api, + }, }, - OpenFGAClient: client, - } + jimmtest.UnsetCredentialStore, // this test relies on credential attributes being stored in postgres + ) - ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - - u, arg, expectedCredential, expectedError := test.createEnv(c, j, client) - user := openfga.NewUser(u, client) + u, arg, expectedCredential, expectedError := test.createEnv(c, j) + user := openfga.NewUser(u, j.OpenFGAClient) user.JimmAdmin = test.jimmAdmin + ctx := context.Background() + result, err := j.UpdateCloudCredential(ctx, user, arg) if expectedError == "" { c.Assert(err, qt.Equals, nil) @@ -811,6 +802,7 @@ func TestUpdateCloudCredential(t *testing.T) { } err = j.Database.GetCloudCredential(ctx, &credential) c.Assert(err, qt.Equals, nil) + c.Assert(credential, jimmtest.DBObjectEquals, expectedCredential) } else { c.Assert(err, qt.ErrorMatches, expectedError) @@ -823,9 +815,6 @@ func TestUpdateCloudCredentialForUnknownUser(t *testing.T) { c := qt.New(t) ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - env := jimmtest.ParseEnvironment(c, `clouds: - name: test-cloud type: `+jimmtest.TestProviderType+` @@ -835,24 +824,18 @@ users: - username: alice@canonical.com controller-access: superuser `) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: &jimmtest.API{}, }, - OpenFGAClient: client, - } + }) - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) u := env.User("alice@canonical.com").DBObject(c, j.Database) - user := openfga.NewUser(&u, client) + user := openfga.NewUser(&u, j.OpenFGAClient) user.JimmAdmin = true - _, err = j.UpdateCloudCredential(ctx, user, jimm.UpdateCloudCredentialArgs{ + _, err := j.UpdateCloudCredential(ctx, user, jimm.UpdateCloudCredentialArgs{ CredentialTag: names.NewCloudCredentialTag("test-cloud/bob@canonical.com/test"), Credential: jujuparams.CloudCredential{ AuthType: "empty", @@ -864,7 +847,6 @@ users: func TestRevokeCloudCredential(t *testing.T) { c := qt.New(t) - now := time.Now().UTC().Round(time.Millisecond) arch := "amd64" mem := uint64(8096) cores := uint64(8) @@ -1238,27 +1220,16 @@ func TestRevokeCloudCredential(t *testing.T) { }, } - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.about) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, }, - OpenFGAClient: client, - } - - ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) - user, tag, expectedError := test.createEnv(c, j, client) + user, tag, expectedError := test.createEnv(c, j, j.OpenFGAClient) - err = j.RevokeCloudCredential(ctx, user, tag, false) + ctx := context.Background() + err := j.RevokeCloudCredential(ctx, user, tag, false) if expectedError == "" { c.Assert(err, qt.Equals, nil) @@ -1276,8 +1247,6 @@ func TestRevokeCloudCredential(t *testing.T) { func TestGetCloudCredential(t *testing.T) { c := qt.New(t) - now := time.Now().UTC().Round(time.Millisecond) - tests := []struct { about string revokeCredentialErrors []error @@ -1372,24 +1341,12 @@ func TestGetCloudCredential(t *testing.T) { }} for _, test := range tests { c.Run(test.about, func(c *qt.C) { - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.about) - c.Assert(err, qt.IsNil) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: client, - } - - ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) - u, tag, expectedCredential, expectedError := test.createEnv(c, j, client) - user := openfga.NewUser(u, client) - credential, err := j.GetCloudCredential(ctx, user, tag) + u, tag, expectedCredential, expectedError := test.createEnv(c, j, j.OpenFGAClient) + user := openfga.NewUser(u, j.OpenFGAClient) + credential, err := j.GetCloudCredential(context.Background(), user, tag) if expectedError == "" { c.Assert(err, qt.Equals, nil) c.Assert(credential, jimmtest.DBObjectEquals, &expectedCredential) @@ -1483,19 +1440,9 @@ func TestForEachUserCloudCredential(t *testing.T) { client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) + env := jimmtest.ParseEnvironment(c, test.env) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: &jimmtest.Dialer{ - API: &jimmtest.API{}, - }, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) u := env.User(test.username).DBObject(c, j.Database) @@ -1622,31 +1569,18 @@ func TestGetCloudCredentialAttributes(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, getCloudCredentialAttributesEnv) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: &jimmtest.Dialer{ - API: &jimmtest.API{}, - }, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) u := env.User("bob@canonical.com").DBObject(c, j.Database) - userBob := openfga.NewUser(&u, client) + userBob := openfga.NewUser(&u, j.OpenFGAClient) credTag := fmt.Sprintf("test-cloud/bob@canonical.com/%s", test.cred) cred, err := j.GetCloudCredential(ctx, userBob, names.NewCloudCredentialTag(credTag)) c.Assert(err, qt.IsNil) u = env.User(test.username).DBObject(c, j.Database) - userTest := openfga.NewUser(&u, client) + userTest := openfga.NewUser(&u, j.OpenFGAClient) userTest.JimmAdmin = test.jimmAdmin attr, redacted, err := j.GetCloudCredentialAttributes(ctx, userTest, cred, test.hidden) if test.expectError != "" { @@ -1673,18 +1607,10 @@ func TestCloudCredentialAttributeStore(t *testing.T) { attrStore := testCloudCredentialAttributeStore{ attrs: make(map[string]map[string]string), } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: &jimmtest.Dialer{ - API: &jimmtest.API{}, - }, + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ CredentialStore: attrStore, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, `clouds: - name: test diff --git a/internal/jimm/clouddefaults_test.go b/internal/jimm/clouddefaults_test.go index 09802944d..b4224e534 100644 --- a/internal/jimm/clouddefaults_test.go +++ b/internal/jimm/clouddefaults_test.go @@ -5,7 +5,6 @@ package jimm_test import ( "context" "testing" - "time" qt "github.com/frankban/quicktest" "github.com/google/go-cmp/cmp/cmpopts" @@ -23,7 +22,6 @@ func TestSetCloudDefaults(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now() type testConfig struct { user *dbmodel.Identity @@ -232,17 +230,11 @@ func TestSetCloudDefaults(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - j := &jimm.JIMM{ - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err := j.Database.Migrate(ctx, true) - c.Assert(err, qt.Equals, nil) + j := jimmtest.NewJIMM(c, nil) testConfig := test.setup(c, j) - err = j.SetModelDefaults(ctx, testConfig.user, testConfig.cloud, testConfig.region, testConfig.defaults) + err := j.SetModelDefaults(ctx, testConfig.user, testConfig.cloud, testConfig.region, testConfig.defaults) if testConfig.expectedError == "" { c.Assert(err, qt.Equals, nil) dbDefaults := dbmodel.CloudDefaults{ @@ -266,7 +258,6 @@ func TestUnsetCloudDefaults(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now() type testConfig struct { user *dbmodel.Identity @@ -425,17 +416,11 @@ func TestUnsetCloudDefaults(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - j := &jimm.JIMM{ - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err := j.Database.Migrate(ctx, true) - c.Assert(err, qt.Equals, nil) + j := jimmtest.NewJIMM(c, nil) testConfig := test.setup(c, j) - err = j.UnsetModelDefaults(ctx, testConfig.user, testConfig.cloud, testConfig.region, testConfig.keys) + err := j.UnsetModelDefaults(ctx, testConfig.user, testConfig.cloud, testConfig.region, testConfig.keys) if testConfig.expectedError == "" { c.Assert(err, qt.Equals, nil) dbDefaults := dbmodel.CloudDefaults{ @@ -459,15 +444,8 @@ func TestModelDefaultsForCloud(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now() - j := &jimm.JIMM{ - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err := j.Database.Migrate(ctx, true) - c.Assert(err, qt.Equals, nil) + j := jimmtest.NewJIMM(c, nil) user, err := dbmodel.NewIdentity("bob@canonical.com") c.Assert(err, qt.IsNil) diff --git a/internal/jimm/controller_test.go b/internal/jimm/controller_test.go index bcab83215..b4bf8acfc 100644 --- a/internal/jimm/controller_test.go +++ b/internal/jimm/controller_test.go @@ -8,7 +8,6 @@ import ( "encoding/json" "sync" "testing" - "time" "github.com/canonical/ofga" qt "github.com/frankban/quicktest" @@ -24,7 +23,6 @@ import ( semversion "github.com/juju/version" "gopkg.in/macaroon.v2" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm" @@ -37,7 +35,6 @@ import ( func TestAddController(t *testing.T) { c := qt.New(t) - now := time.Now().UTC().Round(time.Millisecond) api := &jimmtest.API{ Clouds_: func(context.Context) (map[names.CloudTag]jujuparams.Cloud, error) { clouds := map[names.CloudTag]jujuparams.Cloud{ @@ -130,28 +127,18 @@ func TestAddController(t *testing.T) { }, } - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, }, - OpenFGAClient: client, - } + }) ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) - alice := openfga.NewUser(u, client) + alice := openfga.NewUser(u, j.OpenFGAClient) alice.JimmAdmin = true err = alice.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) @@ -203,7 +190,6 @@ func TestAddControllerWithVault(t *testing.T) { KVPath: path, } - now := time.Now().UTC().Round(time.Millisecond) api := &jimmtest.API{ Clouds_: func(context.Context) (map[names.CloudTag]jujuparams.Cloud, error) { clouds := map[names.CloudTag]jujuparams.Cloud{ @@ -296,29 +282,19 @@ func TestAddControllerWithVault(t *testing.T) { }, } - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, }, CredentialStore: store, - OpenFGAClient: ofgaClient, - } + }) ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) - alice := openfga.NewUser(u, ofgaClient) + alice := openfga.NewUser(u, j.OpenFGAClient) alice.JimmAdmin = true err = alice.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) @@ -403,24 +379,11 @@ func TestEarliestControllerVersion(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: client, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, testEarliestControllerVersionEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) v, err := j.EarliestControllerVersion(ctx) c.Assert(err, qt.Equals, nil) @@ -482,8 +445,6 @@ func TestImportModel(t *testing.T) { c := qt.New(t) trueValue := true - now := time.Now().UTC().Truncate(time.Millisecond) - tests := []struct { about string user string @@ -1015,32 +976,23 @@ func TestImportModel(t *testing.T) { }, } - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.about) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, UUID: test.expectedModel.Controller.UUID, }, - OpenFGAClient: client, - } + }) + ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testImportModelEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.user).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) user.JimmAdmin = test.jimmAdmin - err = j.ImportModel(ctx, user, test.controllerName, names.NewModelTag(test.modelUUID), test.newOwner) + err := j.ImportModel(ctx, user, test.controllerName, names.NewModelTag(test.modelUUID), test.newOwner) if test.expectedError == "" { c.Assert(err, qt.IsNil) @@ -1056,7 +1008,7 @@ func TestImportModel(t *testing.T) { Relation: ofganames.ControllerRelation, Target: ofganames.ConvertTag(names.NewModelTag(test.modelUUID)), } - ok, err := client.CheckRelation(ctx, controllerPermissionCheck, false) + ok, err := j.OpenFGAClient.CheckRelation(ctx, controllerPermissionCheck, false) c.Assert(err, qt.IsNil) c.Assert(ok, qt.IsTrue) @@ -1066,7 +1018,7 @@ func TestImportModel(t *testing.T) { Relation: ofganames.AdministratorRelation, Target: ofganames.ConvertTag(names.NewApplicationOfferTag(offer.UUID)), } - ok, err := client.CheckRelation(ctx, offerPermissionCheck, false) + ok, err := j.OpenFGAClient.CheckRelation(ctx, offerPermissionCheck, false) c.Assert(err, qt.IsNil) c.Assert(ok, qt.IsTrue) } @@ -1139,15 +1091,7 @@ func TestSetControllerConfig(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - } - ctx := context.Background() - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, testControllerConfigEnv) env.PopulateDB(c, j.Database) @@ -1156,7 +1100,9 @@ func TestSetControllerConfig(t *testing.T) { user := openfga.NewUser(&dbUser, nil) user.JimmAdmin = test.jimmAdmin - err = j.SetControllerConfig(ctx, user, test.args) + ctx := context.Background() + + err := j.SetControllerConfig(ctx, user, test.args) if test.expectedError == "" { c.Assert(err, qt.IsNil) @@ -1216,15 +1162,7 @@ func TestGetControllerConfig(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - } - ctx := context.Background() - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, testImportModelEnv) env.PopulateDB(c, j.Database) @@ -1237,7 +1175,9 @@ func TestGetControllerConfig(t *testing.T) { user := openfga.NewUser(&dbUser, nil) user.JimmAdmin = test.jimmAdmin - err = j.SetControllerConfig(ctx, superuser, jujuparams.ControllerConfigSet{ + ctx := context.Background() + + err := j.SetControllerConfig(ctx, superuser, jujuparams.ControllerConfigSet{ Config: map[string]interface{}{ "key1": "value1", }, @@ -1364,20 +1304,13 @@ func TestUpdateMigratedModel(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: &jimmtest.API{ ModelInfo_: test.modelInfo, }, }, - } - ctx := context.Background() - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, testUpdateMigratedModelEnv) env.PopulateDB(c, j.Database) @@ -1386,7 +1319,9 @@ func TestUpdateMigratedModel(t *testing.T) { user := openfga.NewUser(&dbUser, nil) user.JimmAdmin = test.jimmAdmin - err = j.UpdateMigratedModel(ctx, user, test.model, test.targetController) + ctx := context.Background() + + err := j.UpdateMigratedModel(ctx, user, test.model, test.targetController) if test.expectedError != "" { c.Assert(err, qt.ErrorMatches, test.expectedError) } else { @@ -1419,25 +1354,15 @@ users: func TestGetControllerAccess(t *testing.T) { c := qt.New(t) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - OpenFGAClient: client, - } ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testGetControllerAccessEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User("alice@canonical.com").DBObject(c, j.Database) - alice := openfga.NewUser(&dbUser, client) + alice := openfga.NewUser(&dbUser, j.OpenFGAClient) alice.JimmAdmin = true access, err := j.GetJimmControllerAccess(ctx, alice, names.NewUserTag("alice@canonical.com")) @@ -1453,7 +1378,7 @@ func TestGetControllerAccess(t *testing.T) { c.Check(access, qt.Equals, "login") dbUser = env.User("bob@canonical.com").DBObject(c, j.Database) - alice = openfga.NewUser(&dbUser, client) + alice = openfga.NewUser(&dbUser, j.OpenFGAClient) access, err = j.GetJimmControllerAccess(ctx, alice, names.NewUserTag("bob@canonical.com")) c.Assert(err, qt.IsNil) c.Check(access, qt.Equals, "login") @@ -1721,24 +1646,10 @@ func TestInitiateMigration(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - OpenFGAClient: client, - Dialer: &testDialer{}, - } - - ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, testInitiateMigrationEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) c.Patch(jimm.NewControllerClient, func(api base.APICallCloser) jimm.ControllerClient { return &testControllerClient{ @@ -1746,7 +1657,7 @@ func TestInitiateMigration(t *testing.T) { } }) - user := test.user(client) + user := test.user(j.OpenFGAClient) result, err := j.InitiateMigration(context.Background(), user, test.spec) if test.expectedError == "" { @@ -1759,12 +1670,6 @@ func TestInitiateMigration(t *testing.T) { } } -type testDialer struct{} - -func (d *testDialer) Dial(ctx context.Context, ctl *dbmodel.Controller, modelTag names.ModelTag, requiredPermissions map[string]string) (jimm.API, error) { - return (jimm.API)(nil), nil -} - type result struct { err error result any diff --git a/internal/jimm/export_test.go b/internal/jimm/export_test.go index 6e3b27b63..1abcc4ef4 100644 --- a/internal/jimm/export_test.go +++ b/internal/jimm/export_test.go @@ -28,7 +28,7 @@ func WatchController(w *Watcher, ctx context.Context, ctl *dbmodel.Controller) e return w.watchController(ctx, ctl) } -func NewWatcherWithControllerUnavailableChan(db db.Database, dialer Dialer, pubsub Publisher, testChannel chan error) *Watcher { +func NewWatcherWithControllerUnavailableChan(db *db.Database, dialer Dialer, pubsub Publisher, testChannel chan error) *Watcher { return &Watcher{ Pubsub: pubsub, Database: db, @@ -37,7 +37,7 @@ func NewWatcherWithControllerUnavailableChan(db db.Database, dialer Dialer, pubs } } -func NewWatcherWithDeltaProcessedChannel(db db.Database, dialer Dialer, pubsub Publisher, testChannel chan bool) *Watcher { +func NewWatcherWithDeltaProcessedChannel(db *db.Database, dialer Dialer, pubsub Publisher, testChannel chan bool) *Watcher { return &Watcher{ Pubsub: pubsub, Database: db, diff --git a/internal/jimm/group/group_test.go b/internal/jimm/group/group_test.go index d18a76aa9..a74b0bbb7 100644 --- a/internal/jimm/group/group_test.go +++ b/internal/jimm/group/group_test.go @@ -112,7 +112,7 @@ func (s *groupManagerSuite) TestRemoveGroup(c *qt.C) { c.Parallel() ctx := context.Background() - _, group, _, _, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, *s.db) + _, group, _, _, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, s.db) err := s.manager.RemoveGroup(ctx, s.adminUser, group.Name) c.Assert(err, qt.IsNil) @@ -125,7 +125,7 @@ func (s *groupManagerSuite) TestRemoveGroupRemovesTuples(c *qt.C) { c.Parallel() ctx := context.Background() - user, group, controller, model, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, *s.db) + user, group, controller, model, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, s.db) _, err := s.db.AddGroup(ctx, "test-group2") c.Assert(err, qt.IsNil) @@ -191,7 +191,7 @@ func (s *groupManagerSuite) TestRenameGroup(c *qt.C) { c.Parallel() ctx := context.Background() - user, group, controller, model, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, *s.db) + user, group, controller, model, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, s.db) tuples := []openfga.Tuple{ { @@ -265,7 +265,7 @@ func (s *groupManagerSuite) TestListGroups(c *qt.C) { c.Parallel() ctx := context.Background() - user, group, _, _, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, *s.db) + user, group, _, _, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, s.db) u := openfga.NewUser(&user, s.ofgaClient) u.JimmAdmin = true diff --git a/internal/jimm/identity_test.go b/internal/jimm/identity_test.go index 649470a4c..ea6c08ef0 100644 --- a/internal/jimm/identity_test.go +++ b/internal/jimm/identity_test.go @@ -5,15 +5,11 @@ package jimm_test import ( "context" "testing" - "time" qt "github.com/frankban/quicktest" - "github.com/google/uuid" "github.com/canonical/jimm/v3/internal/common/pagination" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" - "github.com/canonical/jimm/v3/internal/jimm" "github.com/canonical/jimm/v3/internal/openfga" "github.com/canonical/jimm/v3/internal/testutils/jimmtest" ) @@ -22,20 +18,7 @@ func TestFetchIdentity(t *testing.T) { c := qt.New(t) ctx := context.Background() - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) user, _, _, _, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) u, err := j.FetchIdentity(ctx, user.Name) @@ -50,22 +33,9 @@ func TestListIdentities(t *testing.T) { c := qt.New(t) ctx := context.Background() - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) - u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, ofgaClient) + u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, j.OpenFGAClient) u.JimmAdmin = true pag := pagination.NewOffsetFilter(10, 0) @@ -135,22 +105,9 @@ func TestCountIdentities(t *testing.T) { c := qt.New(t) ctx := context.Background() - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) - u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, ofgaClient) + u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, j.OpenFGAClient) u.JimmAdmin = true userNames := []string{ diff --git a/internal/jimm/identitymodeldefaults_test.go b/internal/jimm/identitymodeldefaults_test.go index b32008b65..5152aa02d 100644 --- a/internal/jimm/identitymodeldefaults_test.go +++ b/internal/jimm/identitymodeldefaults_test.go @@ -5,7 +5,6 @@ package jimm_test import ( "context" "testing" - "time" qt "github.com/frankban/quicktest" "github.com/google/go-cmp/cmp/cmpopts" @@ -21,7 +20,6 @@ func TestSetIdentityModelDefaults(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now() type testConfig struct { identity *dbmodel.Identity @@ -119,17 +117,11 @@ func TestSetIdentityModelDefaults(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - j := &jimm.JIMM{ - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err := j.Database.Migrate(ctx, true) - c.Assert(err, qt.Equals, nil) + j := jimmtest.NewJIMM(c, nil) testConfig := test.setup(c, j) - err = j.SetIdentityModelDefaults(ctx, testConfig.identity, testConfig.defaults) + err := j.SetIdentityModelDefaults(ctx, testConfig.identity, testConfig.defaults) if testConfig.expectedError == "" { c.Assert(err, qt.Equals, nil) dbDefaults := dbmodel.IdentityModelDefaults{ @@ -149,7 +141,6 @@ func TestIdentityModelDefaults(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now() type testConfig struct { identity *dbmodel.Identity @@ -206,13 +197,7 @@ func TestIdentityModelDefaults(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - j := &jimm.JIMM{ - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err := j.Database.Migrate(ctx, true) - c.Assert(err, qt.Equals, nil) + j := jimmtest.NewJIMM(c, nil) testConfig := test.setup(c, j) diff --git a/internal/jimm/jimm.go b/internal/jimm/jimm.go index 437118a66..a67e6ab80 100644 --- a/internal/jimm/jimm.go +++ b/internal/jimm/jimm.go @@ -29,6 +29,8 @@ import ( "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm/credentials" + "github.com/canonical/jimm/v3/internal/jimm/group" + "github.com/canonical/jimm/v3/internal/jimm/role" "github.com/canonical/jimm/v3/internal/jimmjwx" "github.com/canonical/jimm/v3/internal/openfga" ofganames "github.com/canonical/jimm/v3/internal/openfga/names" @@ -41,58 +43,62 @@ var ( } ) -// A JIMM provides the business logic for managing resources in the JAAS -// system. A single JIMM instance is shared by all concurrent API -// connections therefore the JIMM object itself does not contain any per- -// request state. -type JIMM struct { - // Database is the database used by JIMM, this provides direct access - // to the data store. Any client accessing the database directly is - // responsible for ensuring that the authenticated user has access to - // the data. - Database db.Database - - // Dialer is the API dialer JIMM uses to contact juju controllers. if - // this is not configured all connection attempts will fail. - Dialer Dialer - - // CredentialStore is a store for the attributes of a - // cloud credential and controller credentials. If this is - // not configured then the attributes - // are stored in the standard database. - CredentialStore credentials.CredentialStore - - // Pubsub is a pub-sub hub used for buffering model summaries. - Pubsub *pubsub.Hub +// OAuthAuthenticator is responsible for handling authentication +// via OAuth2.0 AND JWT access tokens to JIMM. +type OAuthAuthenticator interface { + // Device initiates a device flow login and is step ONE of TWO. + // + // This is done via retrieving a: + // - Device code + // - User code + // - VerificationURI + // - Interval + // - Expiry + // From the device /auth endpoint. + // + // The verification uri and user code is sent to the user, as they must enter the code + // into the uri. + // + // The interval, expiry and device code and used to poll the token endpoint for completion. + Device(ctx context.Context) (*oauth2.DeviceAuthResponse, error) - // ReservedCloudNames is the list of names that cannot be used for - // hosted clouds. If this is empty then DefaultReservedCloudNames - // is used. - ReservedCloudNames []string + // DeviceAccessToken continues and collect an access token during the device login flow + // and is step TWO. + // + // See Device(...) godoc for more info pertaining to the flow. + DeviceAccessToken(ctx context.Context, res *oauth2.DeviceAuthResponse) (*oauth2.Token, error) - // UUID holds the UUID of the JIMM controller. - UUID string + // ExtractAndVerifyIDToken extracts the id token from the extras claims of an oauth2 token + // and performs signature verification of the token. + ExtractAndVerifyIDToken(ctx context.Context, oauth2Token *oauth2.Token) (*oidc.IDToken, error) - // OpenFGAClient holds the client used to interact - // with the OpenFGA ReBAC system. - OpenFGAClient *openfga.OFGAClient + // Email retrieves the users email from an id token via the email claim + Email(idToken *oidc.IDToken) (string, error) - // JWKService holds a service responsible for generating and delivering a JWKS - // for consumption within Juju controllers. - JWKService *jimmjwx.JWKSService + // MintSessionToken mints a session token to be used when logging into JIMM + // via an access token. The token only contains the user's email for authentication. + MintSessionToken(email string) (string, error) - // JWTService is responsible for minting JWTs to access controllers. - JWTService *jimmjwx.JWTService + // VerifySessionToken symmetrically verifies the validty of the signature on the + // access token JWT, returning the parsed token. + // + // The subject of the token contains the user's email and can be used + // for user object creation. + // If verification fails, return error with code CodeInvalidSessionToken + // to indicate to the client to retry login. + VerifySessionToken(token string) (jwt.Token, error) - // OAuthAuthenticator is responsible for handling authentication - // via OAuth2.0 AND JWT access tokens to JIMM. - OAuthAuthenticator OAuthAuthenticator + // UpdateIdentity updates the database with the display name and access token set for the user. + // And, if present, a refresh token. + UpdateIdentity(ctx context.Context, email string, token *oauth2.Token) error - // RoleManager provides a means to manage roles within JIMM. - RoleManager RoleManager + // VerifyClientCredentials verifies the provided client ID and client secret. + VerifyClientCredentials(ctx context.Context, clientID string, clientSecret string) error - // RoleManager provides a means to manage groups within JIMM. - GroupManager GroupManager + // AuthenticateBrowserSession updates the session for a browser, additionally + // retrieving new access tokens upon expiry. If this cannot be done, the cookie + // is deleted and an error is returned. + AuthenticateBrowserSession(ctx context.Context, w http.ResponseWriter, req *http.Request) (context.Context, error) } // RoleManager provides a means to manage roles within JIMM. @@ -133,6 +139,138 @@ type GroupManager interface { CountGroups(ctx context.Context, user *openfga.User) (int, error) } +// Parameters holds the services and static fields passed to the jimm.New() constructor. +// You can provide mock implementations of certain services where necessary for dependency injection. +type Parameters struct { + // Database is the database used by JIMM, this provides direct access + // to the data store. Any client accessing the database directly is + // responsible for ensuring that the authenticated user has access to + // the data. + Database *db.Database + + // Dialer is the API dialer JIMM uses to contact juju controllers. if + // this is not configured all connection attempts will fail. + Dialer Dialer + + // CredentialStore is a store for the attributes of a + // cloud credential and controller credentials. If this is + // not configured then the attributes + // are stored in the standard database. + CredentialStore credentials.CredentialStore + + // Pubsub is a pub-sub hub used for buffering model summaries. + Pubsub *pubsub.Hub + + // ReservedCloudNames is the list of names that cannot be used for + // hosted clouds. If this is empty then DefaultReservedCloudNames + // is used. + ReservedCloudNames []string + + // UUID holds the UUID of the JIMM controller. + UUID string + + // OpenFGAClient holds the client used to interact + // with the OpenFGA ReBAC system. + OpenFGAClient *openfga.OFGAClient + + // JWKService holds a service responsible for generating and delivering a JWKS + // for consumption within Juju controllers. + JWKService *jimmjwx.JWKSService + + // JWTService is responsible for minting JWTs to access controllers. + JWTService *jimmjwx.JWTService + + // OAuthAuthenticator is responsible for handling authentication + // via OAuth2.0 AND JWT access tokens to JIMM. + OAuthAuthenticator OAuthAuthenticator +} + +func (p *Parameters) Validate() error { + if p.Database == nil { + return errors.E("missing database") + } + + if p.Dialer == nil { + return errors.E("missing dialer") + } + + if p.CredentialStore == nil { + return errors.E("missing credential store") + } + + if p.Pubsub == nil { + return errors.E("missing pubsub hub") + } + + if p.UUID == "" { + return errors.E("missing uuid") + } + + if p.OpenFGAClient == nil { + return errors.E("missing openfga client") + } + + if p.JWKService == nil { + return errors.E("missing jwks service") + } + + if p.JWTService == nil { + return errors.E("missing jwt service") + } + + if p.OAuthAuthenticator == nil { + return errors.E("missing oauth authenticator") + } + + return nil +} + +// New returns a new instance of JIMM. +// See [Option] and [Parameters] to better understand how to perform dependency injection. +// Primitives like the dialer or authentication service can be mocked at a low level, +// alternatively top business layer objects like the RoleManager can be mocked instead. +func New(p Parameters) (*JIMM, error) { + if err := p.Validate(); err != nil { + return nil, err + } + + j := &JIMM{ + Parameters: p, + } + + if err := j.Database.Migrate(context.Background(), false); err != nil { + return nil, errors.E(err) + } + + roleManager, err := role.NewRoleManager(j.Database, p.OpenFGAClient) + if err != nil { + return nil, err + } + j.roleManager = roleManager + + groupManager, err := group.NewGroupManager(j.Database, p.OpenFGAClient) + if err != nil { + return nil, err + } + j.groupManager = groupManager + + return j, nil +} + +// A JIMM provides the business logic for managing resources in the JAAS +// system. A single JIMM instance is shared by all concurrent API +// connections therefore the JIMM object itself does not contain any per- +// request state. +type JIMM struct { + Parameters + + // roleManager provides a means to manage roles within JIMM. + roleManager RoleManager + + // groupManager provides a means to manage groups within JIMM. + groupManager GroupManager +} + // ResourceTag returns JIMM's controller tag stating its UUID. func (j *JIMM) ResourceTag() names.ControllerTag { return names.NewControllerTag(j.UUID) @@ -140,7 +278,7 @@ func (j *JIMM) ResourceTag() names.ControllerTag { // DB returns the database used by JIMM. func (j *JIMM) DB() *db.Database { - return &j.Database + return j.Database } // PubsubHub returns the pub-sub hub used for buffering model summaries. @@ -153,70 +291,14 @@ func (j *JIMM) AuthorizationClient() *openfga.OFGAClient { return j.OpenFGAClient } -func (j *JIMM) GetRoleManager() RoleManager { - return j.RoleManager +// RoleManager returns a manager that enables role management. +func (j *JIMM) RoleManager() RoleManager { + return j.roleManager } -func (j *JIMM) GetGroupManager() GroupManager { - return j.GroupManager -} - -// OAuthAuthenticator is responsible for handling authentication -// via OAuth2.0 AND JWT access tokens to JIMM. -type OAuthAuthenticator interface { - // Device initiates a device flow login and is step ONE of TWO. - // - // This is done via retrieving a: - // - Device code - // - User code - // - VerificationURI - // - Interval - // - Expiry - // From the device /auth endpoint. - // - // The verification uri and user code is sent to the user, as they must enter the code - // into the uri. - // - // The interval, expiry and device code and used to poll the token endpoint for completion. - Device(ctx context.Context) (*oauth2.DeviceAuthResponse, error) - - // DeviceAccessToken continues and collect an access token during the device login flow - // and is step TWO. - // - // See Device(...) godoc for more info pertaining to the flow. - DeviceAccessToken(ctx context.Context, res *oauth2.DeviceAuthResponse) (*oauth2.Token, error) - - // ExtractAndVerifyIDToken extracts the id token from the extras claims of an oauth2 token - // and performs signature verification of the token. - ExtractAndVerifyIDToken(ctx context.Context, oauth2Token *oauth2.Token) (*oidc.IDToken, error) - - // Email retrieves the users email from an id token via the email claim - Email(idToken *oidc.IDToken) (string, error) - - // MintSessionToken mints a session token to be used when logging into JIMM - // via an access token. The token only contains the user's email for authentication. - MintSessionToken(email string) (string, error) - - // VerifySessionToken symmetrically verifies the validty of the signature on the - // access token JWT, returning the parsed token. - // - // The subject of the token contains the user's email and can be used - // for user object creation. - // If verification fails, return error with code CodeInvalidSessionToken - // to indicate to the client to retry login. - VerifySessionToken(token string) (jwt.Token, error) - - // UpdateIdentity updates the database with the display name and access token set for the user. - // And, if present, a refresh token. - UpdateIdentity(ctx context.Context, email string, token *oauth2.Token) error - - // VerifyClientCredentials verifies the provided client ID and client secret. - VerifyClientCredentials(ctx context.Context, clientID string, clientSecret string) error - - // AuthenticateBrowserSession updates the session for a browser, additionally - // retrieving new access tokens upon expiry. If this cannot be done, the cookie - // is deleted and an error is returned. - AuthenticateBrowserSession(ctx context.Context, w http.ResponseWriter, req *http.Request) (context.Context, error) +// GroupManager returns a manager that enables group management. +func (j *JIMM) GroupManager() GroupManager { + return j.groupManager } // GetCredentialStore returns the credential store used by JIMM. @@ -649,7 +731,7 @@ func (j *JIMM) FullModelStatus(ctx context.Context, user *openfga.User, modelTag type migrationControllerID = uint -func fillMigrationTarget(db db.Database, credStore credentials.CredentialStore, controllerName string) (jujuparams.MigrationTargetInfo, migrationControllerID, error) { +func fillMigrationTarget(db *db.Database, credStore credentials.CredentialStore, controllerName string) (jujuparams.MigrationTargetInfo, migrationControllerID, error) { dbController := dbmodel.Controller{ Name: controllerName, } diff --git a/internal/jimm/jimm_test.go b/internal/jimm/jimm_test.go index 18ecf5cf8..f57572b2b 100644 --- a/internal/jimm/jimm_test.go +++ b/internal/jimm/jimm_test.go @@ -11,7 +11,6 @@ import ( qt "github.com/frankban/quicktest" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/google/uuid" jujuparams "github.com/juju/juju/rpc/params" "github.com/juju/names/v5" @@ -27,41 +26,27 @@ import ( func TestFindAuditEvents(t *testing.T) { c := qt.New(t) - now := time.Now().UTC().Truncate(time.Microsecond) - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - OpenFGAClient: client, - } + j := jimmtest.NewJIMM(c, nil) ctx := context.Background() - err = j.Database.Migrate(ctx, true) - c.Assert(err, qt.Equals, nil) - alice, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) - admin := openfga.NewUser(alice, client) + admin := openfga.NewUser(alice, j.OpenFGAClient) err = admin.SetControllerAccess(ctx, j.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) bob, err := dbmodel.NewIdentity("bob@canonical.com") c.Assert(err, qt.IsNil) - privileged := openfga.NewUser(bob, client) + privileged := openfga.NewUser(bob, j.OpenFGAClient) err = privileged.SetControllerAccess(ctx, j.ResourceTag(), ofganames.AuditLogViewerRelation) c.Assert(err, qt.IsNil) eve, err := dbmodel.NewIdentity("eve@canonical.com") c.Assert(err, qt.IsNil) - unprivileged := openfga.NewUser(eve, client) + unprivileged := openfga.NewUser(eve, j.OpenFGAClient) events := []dbmodel.AuditLogEntry{{ Time: now, @@ -211,16 +196,9 @@ func TestControllerInfo(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) + env := jimmtest.ParseEnvironment(c, testControllersEnv) env.PopulateDB(c, j.Database) @@ -236,24 +214,11 @@ func TestListControllers(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: client, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, testControllersEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) tests := []struct { about string @@ -282,7 +247,7 @@ func TestListControllers(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - user := openfga.NewUser(&test.user, client) + user := openfga.NewUser(&test.user, j.OpenFGAClient) user.JimmAdmin = test.jimmAdmin controllers, err := j.ListControllers(ctx, user) if test.expectedError != "" { @@ -324,24 +289,11 @@ func TestSetControllerDeprecated(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, testSetControllerDeprecatedEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) tests := []struct { about string @@ -368,7 +320,7 @@ func TestSetControllerDeprecated(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - user := openfga.NewUser(&test.user, client) + user := openfga.NewUser(&test.user, j.OpenFGAClient) user.JimmAdmin = test.jimmAdmin err := j.SetControllerDeprecated(ctx, user, "test1", test.deprecated) if test.expectedError != "" { @@ -438,7 +390,6 @@ func TestRemoveController(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) tests := []struct { about string @@ -480,16 +431,7 @@ func TestRemoveController(t *testing.T) { for _, test := range tests { c.Run(test.about, func(c *qt.C) { - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, removeControllerTestEnv) env.PopulateDB(c, j.Database) @@ -505,11 +447,11 @@ func TestRemoveController(t *testing.T) { Valid: true, Time: *test.unavailableSince, } - err = j.Database.UpdateController(ctx, &controller) + err := j.Database.UpdateController(ctx, &controller) c.Assert(err, qt.Equals, nil) } - err = j.RemoveController(ctx, user, "controller-1", test.force) + err := j.RemoveController(ctx, user, "controller-1", test.force) if test.expectedError != "" { c.Assert(err, qt.ErrorMatches, test.expectedError) } else { @@ -575,17 +517,8 @@ models: func TestRemoveAndAddController(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - } - - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, removeAndAddControllerTestEnv) env.PopulateDB(c, j.Database) @@ -595,7 +528,7 @@ func TestRemoveAndAddController(t *testing.T) { user := openfga.NewUser(&dbUser, nil) user.JimmAdmin = true - err = j.RemoveController(ctx, user, "controller-1", true) + err := j.RemoveController(ctx, user, "controller-1", true) c.Assert(err, qt.Equals, nil) ctls, err := j.ListControllers(ctx, user) c.Assert(err, qt.Equals, nil) @@ -648,7 +581,6 @@ func TestFullModelStatus(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) fullStatus := jujuparams.FullStatus{ Model: jujuparams.ModelStatusInfo{ @@ -734,18 +666,11 @@ func TestFullModelStatus(t *testing.T) { Status_: test.statusFunc, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, }, - } - - err := j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, fullModelStatusTestEnv) env.PopulateDB(c, j.Database) @@ -781,7 +706,6 @@ func TestFillMigrationTarget(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) tests := []struct { about string @@ -808,7 +732,7 @@ func TestFillMigrationTarget(t *testing.T) { } for _, test := range tests { c.Run(test.about, func(c *qt.C) { - db := db.Database{ + db := &db.Database{ DB: jimmtest.PostgresDB(c, func() time.Time { return now }), } err := db.Migrate(ctx, false) @@ -869,7 +793,6 @@ func TestInitiateInternalMigration(t *testing.T) { c := qt.New(t) ctx := context.Background() - now := time.Now().UTC().Round(time.Millisecond) tests := []struct { about string @@ -889,7 +812,6 @@ func TestInitiateInternalMigration(t *testing.T) { } for _, test := range tests { c.Run(test.about, func(c *qt.C) { - c.Patch(jimm.InitiateMigration, func(ctx context.Context, j *jimm.JIMM, user *openfga.User, spec jujuparams.MigrationSpec) (jujuparams.InitiateMigrationResult, error) { return jujuparams.InitiateMigrationResult{}, nil }) @@ -897,20 +819,13 @@ func TestInitiateInternalMigration(t *testing.T) { err := store.PutControllerCredentials(context.Background(), test.migrateInfo.TargetController, "admin", "test-secret") c.Assert(err, qt.IsNil) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ CredentialStore: store, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, InitiateMigrationTestEnv) env.PopulateDB(c, j.Database) - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + dbUser := env.User(test.user).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, nil) mt, err := names.ParseModelTag(test.migrateInfo.ModelTag) diff --git a/internal/jimm/model_status_parser_test.go b/internal/jimm/model_status_parser_test.go index b1b361e83..cee26e97c 100644 --- a/internal/jimm/model_status_parser_test.go +++ b/internal/jimm/model_status_parser_test.go @@ -7,13 +7,11 @@ import ( "time" qt "github.com/frankban/quicktest" - "github.com/google/uuid" "github.com/juju/juju/core/life" "github.com/juju/juju/core/status" jujuparams "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm" "github.com/canonical/jimm/v3/internal/testutils/jimmtest" @@ -326,15 +324,7 @@ func TestQueryModelsJq(t *testing.T) { ctx := context.Background() // Test setup - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: client, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: jimmtest.ModelDialerMap{ "10000000-0000-0000-0000-000000000000": &jimmtest.Dialer{ API: &jimmtest.API{ @@ -429,10 +419,7 @@ func TestQueryModelsJq(t *testing.T) { }, }, }, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, crossModelQueryEnv) env.PopulateDB(c, j.Database) diff --git a/internal/jimm/model_test.go b/internal/jimm/model_test.go index 1a4321495..2fcc7c830 100644 --- a/internal/jimm/model_test.go +++ b/internal/jimm/model_test.go @@ -13,7 +13,6 @@ import ( qt "github.com/frankban/quicktest" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/google/uuid" "github.com/juju/juju/core/life" jujuparams "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" @@ -21,7 +20,6 @@ import ( "github.com/juju/version/v2" "sigs.k8s.io/yaml" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/errors" "github.com/canonical/jimm/v3/internal/jimm" @@ -1077,38 +1075,27 @@ func TestAddModel(t *testing.T) { for _, test := range addModelTests { c.Run(test.name, func(c *qt.C) { - api := &jimmtest.API{ - UpdateCredential_: test.updateCredential, - GrantJIMMModelAdmin_: test.grantJIMMModelAdmin, - CreateModel_: test.createModel, - } - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ - API: api, + API: &jimmtest.API{ + UpdateCredential_: test.updateCredential, + GrantJIMMModelAdmin_: test.grantJIMMModelAdmin, + CreateModel_: test.createModel, + }, }, - OpenFGAClient: client, - } + }) + ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) user.JimmAdmin = test.jimmAdmin args := jimm.ModelCreateArgs{} - err = args.FromJujuModelCreateArgs(&test.args) + err := args.FromJujuModelCreateArgs(&test.args) c.Assert(err, qt.IsNil) _, err = j.AddModel(context.Background(), user, &args) @@ -1133,7 +1120,7 @@ func TestAddModel(t *testing.T) { context.Background(), openfga.NewUser( ownerIdentity, - client, + j.OpenFGAClient, ), m1.ResourceTag(), ) @@ -1220,21 +1207,10 @@ func TestGetModel(t *testing.T) { ctx := context.Background() c := qt.New(t) - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), t.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, getModelTestEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) // Get model model, err := j.GetModel(ctx, env.Models[0].UUID) @@ -1427,17 +1403,7 @@ func TestModelInfo(t *testing.T) { for _, test := range modelInfoTests { c.Run(test.name, func(c *qt.C) { - ctx := context.Background() - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: &jimmtest.API{ ModelInfo_: func(_ context.Context, mi *jujuparams.ModelInfo) error { @@ -1483,17 +1449,15 @@ func TestModelInfo(t *testing.T) { }, }, }, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser, err := dbmodel.NewIdentity(test.username) c.Assert(err, qt.IsNil) - user := openfga.NewUser(dbUser, client) + user := openfga.NewUser(dbUser, j.OpenFGAClient) mi, err := j.ModelInfo(context.Background(), user, names.NewModelTag(test.uuid)) if test.expectError != "" { @@ -1606,33 +1570,20 @@ func TestModelStatus(t *testing.T) { for _, test := range modelStatusTests { c.Run(test.name, func(c *qt.C) { - ctx := context.Background() - dialer := &jimmtest.Dialer{ API: &jimmtest.API{ ModelStatus_: test.modelStatus, }, } - - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: dialer, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) ms, err := j.ModelStatus(context.Background(), user, names.NewModelTag(test.uuid)) if test.expectError != "" { @@ -1764,31 +1715,16 @@ func TestForEachUserModel(t *testing.T) { c := qt.New(t) ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: &jimmtest.Dialer{ - API: &jimmtest.API{}, - }, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, forEachModelTestEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User("bob@canonical.com").DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) var res []jujuparams.ModelSummaryResult - err = j.ForEachUserModel(ctx, user, func(m *dbmodel.Model, access jujuparams.UserAccessPermission) error { + err := j.ForEachUserModel(ctx, user, func(m *dbmodel.Model, access jujuparams.UserAccessPermission) error { s := m.ToJujuModelSummary() s.UserAccess = access res = append(res, jujuparams.ModelSummaryResult{Result: &s}) @@ -1904,36 +1840,22 @@ func TestForEachModel(t *testing.T) { c := qt.New(t) ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: &jimmtest.Dialer{ - API: &jimmtest.API{}, - }, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) env := jimmtest.ParseEnvironment(c, forEachModelTestEnv) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User("bob@canonical.com").DBObject(c, j.Database) - bob := openfga.NewUser(&dbUser, client) + bob := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.ForEachModel(ctx, bob, func(_ *dbmodel.Model, _ jujuparams.UserAccessPermission) error { + err := j.ForEachModel(ctx, bob, func(_ *dbmodel.Model, _ jujuparams.UserAccessPermission) error { return errors.E("function called unexpectedly") }) c.Check(err, qt.ErrorMatches, `unauthorized`) c.Assert(errors.ErrorCode(err), qt.Equals, errors.CodeUnauthorized) dbUser = env.User("alice@canonical.com").DBObject(c, j.Database) - alice := openfga.NewUser(&dbUser, client) + alice := openfga.NewUser(&dbUser, j.OpenFGAClient) alice.JimmAdmin = true var models []string @@ -2175,30 +2097,21 @@ func TestGrantModelAccess(t *testing.T) { c.Run(tt.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), tt.name) - c.Assert(err, qt.IsNil) - - env := jimmtest.ParseEnvironment(c, tt.env) dialer := &jimmtest.Dialer{ API: &jimmtest.API{}, Err: tt.dialError, } - j := &jimm.JIMM{ - UUID: jimmtest.ControllerUUID, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) + + env := jimmtest.ParseEnvironment(c, tt.env) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(tt.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.GrantModelAccess(ctx, user, names.NewModelTag(tt.uuid), names.NewUserTag(tt.targetUsername), jujuparams.UserAccessPermission(tt.access)) + err := j.GrantModelAccess(ctx, user, names.NewModelTag(tt.uuid), names.NewUserTag(tt.targetUsername), jujuparams.UserAccessPermission(tt.access)) c.Assert(dialer.IsClosed(), qt.IsTrue) if tt.expectError != "" { c.Check(err, qt.ErrorMatches, tt.expectError) @@ -2209,7 +2122,7 @@ func TestGrantModelAccess(t *testing.T) { } c.Assert(err, qt.IsNil) for _, tuple := range tt.expectRelations { - value, err := client.CheckRelation(ctx, tuple, false) + value, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(value, qt.IsTrue, qt.Commentf("expected the tuple to exist after granting")) } @@ -2896,43 +2809,34 @@ func TestRevokeModelAccess(t *testing.T) { c.Run(tt.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), tt.name) - c.Assert(err, qt.IsNil) - - env := jimmtest.ParseEnvironment(c, tt.env) dialer := &jimmtest.Dialer{ API: &jimmtest.API{}, Err: tt.dialError, } - j := &jimm.JIMM{ - UUID: jimmtest.ControllerUUID, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, - Dialer: dialer, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Dialer: dialer, + }) + + env := jimmtest.ParseEnvironment(c, tt.env) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) if len(tt.extraInitialTuples) > 0 { - err = client.AddRelation(ctx, tt.extraInitialTuples...) + err := j.OpenFGAClient.AddRelation(ctx, tt.extraInitialTuples...) c.Assert(err, qt.IsNil) } if tt.expectRemovedRelations != nil { for _, tuple := range tt.expectRemovedRelations { - value, err := client.CheckRelation(ctx, tuple, false) + value, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(value, qt.IsTrue, qt.Commentf("expected the tuple to exist before revoking")) } } dbUser := env.User(tt.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.RevokeModelAccess(ctx, user, names.NewModelTag(tt.uuid), names.NewUserTag(tt.targetUsername), jujuparams.UserAccessPermission(tt.access)) + err := j.RevokeModelAccess(ctx, user, names.NewModelTag(tt.uuid), names.NewUserTag(tt.targetUsername), jujuparams.UserAccessPermission(tt.access)) c.Assert(dialer.IsClosed(), qt.IsTrue) if tt.expectError != "" { c.Check(err, qt.ErrorMatches, tt.expectError) @@ -2944,14 +2848,14 @@ func TestRevokeModelAccess(t *testing.T) { c.Assert(err, qt.IsNil) if tt.expectRemovedRelations != nil { for _, tuple := range tt.expectRemovedRelations { - value, err := client.CheckRelation(ctx, tuple, false) + value, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(value, qt.IsFalse, qt.Commentf("expected the tuple to be removed after revoking")) } } if tt.expectRelations != nil { for _, tuple := range tt.expectRelations { - value, err := client.CheckRelation(ctx, tuple, false) + value, err := j.OpenFGAClient.CheckRelation(ctx, tuple, false) c.Assert(err, qt.IsNil) c.Assert(value, qt.IsTrue, qt.Commentf("expected the tuple to exist after revoking")) } @@ -3087,27 +2991,17 @@ func TestDestroyModel(t *testing.T) { Err: test.dialError, } - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: dialer, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.DestroyModel(ctx, user, names.NewModelTag(test.uuid), test.destroyStorage, test.force, test.maxWait, test.timeout) + err := j.DestroyModel(ctx, user, names.NewModelTag(test.uuid), test.destroyStorage, test.force, test.maxWait, test.timeout) c.Assert(dialer.IsClosed(), qt.IsTrue) if test.expectError != "" { c.Check(err, qt.ErrorMatches, test.expectError) @@ -3206,32 +3100,21 @@ func TestDumpModel(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - dialer := &jimmtest.Dialer{ API: &jimmtest.API{ DumpModel_: test.dumpModel, }, Err: test.dialError, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: dialer, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) s, err := j.DumpModel(ctx, user, names.NewModelTag(test.uuid), test.simplified) c.Assert(dialer.IsClosed(), qt.IsTrue) @@ -3319,32 +3202,21 @@ func TestDumpModelDB(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - dialer := &jimmtest.Dialer{ API: &jimmtest.API{ DumpModelDB_: test.dumpModelDB, }, Err: test.dialError, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: dialer, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) dump, err := j.DumpModelDB(ctx, user, names.NewModelTag(test.uuid)) c.Assert(dialer.IsClosed(), qt.IsTrue) @@ -3437,9 +3309,6 @@ func TestValidateModelUpgrade(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - dialer := &jimmtest.Dialer{ API: &jimmtest.API{ ValidateModelUpgrade_: test.validateModelUpgrade, @@ -3447,25 +3316,17 @@ func TestValidateModelUpgrade(t *testing.T) { Err: test.dialError, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: dialer, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.ValidateModelUpgrade(ctx, user, names.NewModelTag(test.uuid), test.force) + err := j.ValidateModelUpgrade(ctx, user, names.NewModelTag(test.uuid), test.force) c.Assert(dialer.IsClosed(), qt.IsTrue) if test.expectError != "" { c.Assert(err, qt.ErrorMatches, test.expectError) @@ -3660,9 +3521,6 @@ func TestUpdateModelCredential(t *testing.T) { c.Run(test.name, func(c *qt.C) { ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name(), test.name) - c.Assert(err, qt.IsNil) - dialer := &jimmtest.Dialer{ API: &jimmtest.API{ UpdateCredential_: test.updateCredential, @@ -3670,24 +3528,17 @@ func TestUpdateModelCredential(t *testing.T) { }, Err: test.dialError, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: dialer, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + }) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User(test.username).DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) - err = j.ChangeModelCredential( + err := j.ChangeModelCredential( ctx, user, names.NewModelTag(test.uuid), @@ -3739,22 +3590,13 @@ users: `[1:]), } - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - j := &jimm.JIMM{ - UUID: uuid.NewString(), - OpenFGAClient: client, - Database: db.Database{ - DB: jimmtest.PostgresDB(c, nil), - }, + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, }, - } + }) + ctx := context.Background() - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) envDefinition := ` clouds: @@ -3798,15 +3640,15 @@ controllers: priority: 1 ` env := jimmtest.ParseEnvironment(c, envDefinition) - env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, j.OpenFGAClient) dbUser := env.User("alice@canonical.com").DBObject(c, j.Database) - user := openfga.NewUser(&dbUser, client) + user := openfga.NewUser(&dbUser, j.OpenFGAClient) controller := dbmodel.Controller{ Name: "controller-1", } - err = j.Database.GetController(ctx, &controller) + err := j.Database.GetController(ctx, &controller) c.Assert(err, qt.IsNil) err = j.Database.DeleteController(ctx, &controller) diff --git a/internal/jimm/relation_test.go b/internal/jimm/relation_test.go index 64255a9b9..2951d9c26 100644 --- a/internal/jimm/relation_test.go +++ b/internal/jimm/relation_test.go @@ -5,15 +5,11 @@ package jimm_test import ( "context" "testing" - "time" qt "github.com/frankban/quicktest" - "github.com/google/uuid" "github.com/canonical/jimm/v3/internal/common/pagination" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" - "github.com/canonical/jimm/v3/internal/jimm" "github.com/canonical/jimm/v3/internal/openfga" "github.com/canonical/jimm/v3/internal/openfga/names" "github.com/canonical/jimm/v3/internal/testutils/jimmtest" @@ -25,28 +21,14 @@ func TestListRelationshipTuples(t *testing.T) { c := qt.New(t) ctx := context.Background() - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } + j := jimmtest.NewJIMM(c, nil) - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - - u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, ofgaClient) + u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, j.OpenFGAClient) u.JimmAdmin = true user, _, controller, model, _, _, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) - c.Assert(err, qt.IsNil) - err = j.AddRelation(ctx, u, []apiparams.RelationshipTuple{ + err := j.AddRelation(ctx, u, []apiparams.RelationshipTuple{ { Object: user.Tag().String(), Relation: names.ReaderRelation.String(), @@ -174,28 +156,14 @@ func TestListObjectRelations(t *testing.T) { c := qt.New(t) ctx := context.Background() - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } + j := jimmtest.NewJIMM(c, nil) - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) - - u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, ofgaClient) + u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, j.OpenFGAClient) u.JimmAdmin = true user, group, controller, model, _, cloud, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) - c.Assert(err, qt.IsNil) - err = j.AddRelation(ctx, u, []apiparams.RelationshipTuple{ + err := j.AddRelation(ctx, u, []apiparams.RelationshipTuple{ { Object: user.Tag().String(), Relation: names.ReaderRelation.String(), diff --git a/internal/jimm/resource_test.go b/internal/jimm/resource_test.go index abf436300..c44f2b0c5 100644 --- a/internal/jimm/resource_test.go +++ b/internal/jimm/resource_test.go @@ -4,15 +4,11 @@ package jimm_test import ( "context" "testing" - "time" qt "github.com/frankban/quicktest" - "github.com/google/uuid" "github.com/canonical/jimm/v3/internal/common/pagination" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" - "github.com/canonical/jimm/v3/internal/jimm" "github.com/canonical/jimm/v3/internal/openfga" "github.com/canonical/jimm/v3/internal/testutils/jimmtest" ) @@ -20,25 +16,14 @@ import ( func TestGetResources(t *testing.T) { c := qt.New(t) ctx := context.Background() - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - now := time.Now().UTC().Round(time.Millisecond) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: ofgaClient, - } + j := jimmtest.NewJIMM(c, nil) - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) _, _, controller, model, applicationOffer, cloud, _, _ := jimmtest.CreateTestControllerEnvironment(ctx, c, j.Database) ids := []string{applicationOffer.UUID, cloud.Name, controller.UUID, model.UUID.String} - u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, ofgaClient) + u := openfga.NewUser(&dbmodel.Identity{Name: "admin@canonical.com"}, j.OpenFGAClient) u.JimmAdmin = true testCases := []struct { diff --git a/internal/jimm/service_account_test.go b/internal/jimm/service_account_test.go index e73830ffc..6e09ee628 100644 --- a/internal/jimm/service_account_test.go +++ b/internal/jimm/service_account_test.go @@ -5,17 +5,13 @@ package jimm_test import ( "context" "testing" - "time" qt "github.com/frankban/quicktest" - "github.com/google/uuid" jujuparams "github.com/juju/juju/rpc/params" "github.com/juju/names/v5" - "github.com/canonical/jimm/v3/internal/db" "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/jimm" - "github.com/canonical/jimm/v3/internal/jimm/group" "github.com/canonical/jimm/v3/internal/openfga" ofganames "github.com/canonical/jimm/v3/internal/openfga/names" "github.com/canonical/jimm/v3/internal/testutils/jimmtest" @@ -26,18 +22,14 @@ func TestAddServiceAccount(t *testing.T) { c := qt.New(t) ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - j := &jimm.JIMM{ - OpenFGAClient: client, - } - c.Assert(err, qt.IsNil) + + j := jimmtest.NewJIMM(c, nil) bob, err := dbmodel.NewIdentity("bob@canonical.com") c.Assert(err, qt.IsNil) user := openfga.NewUser( bob, - client, + j.OpenFGAClient, ) clientID := "39caae91-b914-41ae-83f8-c7b86ca5ad5a@serviceaccount" err = j.AddServiceAccount(ctx, user, clientID) @@ -49,7 +41,7 @@ func TestAddServiceAccount(t *testing.T) { c.Assert(err, qt.IsNil) userAlice := openfga.NewUser( alive, - client, + j.OpenFGAClient, ) err = j.AddServiceAccount(ctx, userAlice, clientID) c.Assert(err, qt.ErrorMatches, "service account already owned") @@ -59,8 +51,7 @@ func TestCopyServiceAccountCredential(t *testing.T) { c := qt.New(t) ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) + api := &jimmtest.API{ CheckCredentialModels_: func(context.Context, jujuparams.TaggedCredential) ([]jujuparams.UpdateCredentialModelResult, error) { return []jujuparams.UpdateCredentialModelResult{}, nil @@ -69,29 +60,23 @@ func TestCopyServiceAccountCredential(t *testing.T) { return []jujuparams.UpdateCredentialModelResult{}, nil }, } - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, + + j := jimmtest.NewJIMM(c, &jimm.Parameters{ Dialer: &jimmtest.Dialer{ API: api, }, - OpenFGAClient: client, - } + }) - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) svcAccId, err := dbmodel.NewIdentity("39caae91-b914-41ae-83f8-c7b86ca5ad5a@serviceaccount") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&svcAccId).Error, qt.IsNil) - svcAcc := openfga.NewUser(svcAccId, client) + svcAcc := openfga.NewUser(svcAccId, j.OpenFGAClient) u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil) - user := openfga.NewUser(u, client) + user := openfga.NewUser(u, j.OpenFGAClient) err = user.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) @@ -147,26 +132,17 @@ func TestCopyServiceAccountCredentialWithMissingCredential(t *testing.T) { c := qt.New(t) ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - j := &jimm.JIMM{ - UUID: uuid.NewString(), - Database: db.Database{ - DB: jimmtest.PostgresDB(c, func() time.Time { return now }), - }, - OpenFGAClient: client, - } - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) + svcAccId, err := dbmodel.NewIdentity("39caae91-b914-41ae-83f8-c7b86ca5ad5a@serviceaccount") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&svcAccId).Error, qt.IsNil) - svcAcc := openfga.NewUser(svcAccId, client) + svcAcc := openfga.NewUser(svcAccId, j.OpenFGAClient) u, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, qt.IsNil) c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil) - user := openfga.NewUser(u, client) + user := openfga.NewUser(u, j.OpenFGAClient) cred := dbmodel.CloudCredential{ Name: "test-credential-1", @@ -234,44 +210,32 @@ func TestGrantServiceAccountAccess(t *testing.T) { for _, test := range tests { test := test c.Run(test.about, func(c *qt.C) { - ofgaClient, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - pgDb := db.Database{ - DB: jimmtest.PostgresDB(c, nil), - } - err = pgDb.Migrate(context.Background(), false) - c.Assert(err, qt.IsNil) - groupManager, err := group.NewGroupManager(&pgDb, ofgaClient) - c.Assert(err, qt.IsNil) - jimm := &jimm.JIMM{ - Database: pgDb, - OpenFGAClient: ofgaClient, - GroupManager: groupManager, - } + j := jimmtest.NewJIMM(c, nil) + var u dbmodel.Identity u.SetTag(names.NewUserTag(test.clientID)) - svcAccountIdentity := openfga.NewUser(&u, ofgaClient) + svcAccountIdentity := openfga.NewUser(&u, j.OpenFGAClient) svcAccountIdentity.JimmAdmin = true if len(test.addGroups) > 0 { for _, name := range test.addGroups { - _, err := jimm.GroupManager.AddGroup(context.Background(), svcAccountIdentity, name) + _, err := j.GroupManager().AddGroup(context.Background(), svcAccountIdentity, name) c.Assert(err, qt.IsNil) } } svcAccountTag := jimmnames.NewServiceAccountTag(test.clientID) - err = jimm.GrantServiceAccountAccess(context.Background(), svcAccountIdentity, svcAccountTag, test.tags) + err := j.GrantServiceAccountAccess(context.Background(), svcAccountIdentity, svcAccountTag, test.tags) if test.expectedError == "" { c.Assert(err, qt.IsNil) for _, tag := range test.tags { - parsedTag, err := jimm.ParseAndValidateTag(context.Background(), tag) + parsedTag, err := j.ParseAndValidateTag(context.Background(), tag) c.Assert(err, qt.IsNil) tuple := openfga.Tuple{ Object: parsedTag, Relation: ofganames.AdministratorRelation, Target: ofganames.ConvertTag(jimmnames.NewServiceAccountTag(test.clientID)), } - ok, err := jimm.OpenFGAClient.CheckRelation(context.Background(), tuple, false) + ok, err := j.OpenFGAClient.CheckRelation(context.Background(), tuple, false) c.Assert(err, qt.IsNil) c.Assert(ok, qt.IsTrue) } diff --git a/internal/jimm/user_test.go b/internal/jimm/user_test.go index c4e7a8496..1fbba112b 100644 --- a/internal/jimm/user_test.go +++ b/internal/jimm/user_test.go @@ -21,20 +21,8 @@ func TestGetUser(t *testing.T) { c := qt.New(t) ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) - db := &db.Database{ - DB: jimmtest.PostgresDB(c, time.Now), - } - j := &jimm.JIMM{ - UUID: "test", - Database: *db, - OpenFGAClient: client, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, nil) ofgaUser, err := j.GetUser(ctx, "bob@canonical.com.com") c.Assert(err, qt.IsNil) @@ -68,23 +56,17 @@ func TestUpdateUserLastLogin(t *testing.T) { c := qt.New(t) ctx := context.Background() - client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name()) - c.Assert(err, qt.IsNil) + now := time.Now().Truncate(time.Millisecond) db := &db.Database{ DB: jimmtest.PostgresDB(c, func() time.Time { return now }), } - j := &jimm.JIMM{ - UUID: "test", - Database: *db, - OpenFGAClient: client, - } - - err = j.Database.Migrate(ctx, false) - c.Assert(err, qt.IsNil) + j := jimmtest.NewJIMM(c, &jimm.Parameters{ + Database: db, + }) - err = j.UpdateUserLastLogin(ctx, "bob@canonical.com.com") + err := j.UpdateUserLastLogin(ctx, "bob@canonical.com.com") c.Assert(err, qt.IsNil) user := dbmodel.Identity{Name: "bob@canonical.com.com"} err = j.Database.GetIdentity(ctx, &user) diff --git a/internal/jimm/watcher.go b/internal/jimm/watcher.go index a35a38271..e3a37b15c 100644 --- a/internal/jimm/watcher.go +++ b/internal/jimm/watcher.go @@ -28,7 +28,7 @@ type Publisher interface { // A Watcher watches juju controllers for changes to all models. type Watcher struct { // Database is the database used by the Watcher. - Database db.Database + Database *db.Database // Dialer is the API dialer JIMM uses to contact juju controllers. if // this is not configured all connection attempts will fail. diff --git a/internal/jimm/watcher_test.go b/internal/jimm/watcher_test.go index ba8b0c2a6..a6cf2478d 100644 --- a/internal/jimm/watcher_test.go +++ b/internal/jimm/watcher_test.go @@ -86,9 +86,9 @@ models: var watcherTests = []struct { name string - initDB func(*qt.C, db.Database) + initDB func(*qt.C, *db.Database) deltas [][]jujuparams.Delta - checkDB func(*qt.C, db.Database) + checkDB func(*qt.C, *db.Database) }{{ name: "AddMachine", deltas: [][]jujuparams.Delta{ @@ -104,7 +104,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -140,7 +140,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -176,7 +176,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -202,7 +202,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -233,7 +233,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -265,7 +265,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -292,7 +292,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -328,7 +328,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -376,7 +376,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -400,7 +400,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -424,7 +424,7 @@ var watcherTests = []struct { }}, nil, }, - checkDB: func(c *qt.C, db db.Database) { + checkDB: func(c *qt.C, db *db.Database) { ctx := context.Background() model := dbmodel.Model{ @@ -481,7 +481,7 @@ func TestWatcher(t *testing.T) { deltaProcessedChannel := make(chan bool, len(test.deltas)) w := jimm.NewWatcherWithDeltaProcessedChannel( - db.Database{ + &db.Database{ DB: jimmtest.PostgresDB(c, nil), }, &jimmtest.Dialer{ @@ -645,7 +645,7 @@ func TestModelSummaryWatcher(t *testing.T) { w := &jimm.Watcher{ Pubsub: publisher, - Database: db.Database{ + Database: &db.Database{ DB: jimmtest.PostgresDB(c, nil), }, Dialer: &jimmtest.Dialer{ @@ -728,7 +728,7 @@ func TestWatcherSetsControllerUnavailable(t *testing.T) { controllerUnavailableChannel := make(chan error, 1) w := jimm.NewWatcherWithControllerUnavailableChan( - db.Database{ + &db.Database{ DB: jimmtest.PostgresDB(c, nil), }, &jimmtest.Dialer{ @@ -774,7 +774,7 @@ func TestWatcherClearsControllerUnavailable(t *testing.T) { defer cancel() w := jimm.Watcher{ - Database: db.Database{ + Database: &db.Database{ DB: jimmtest.PostgresDB(c, nil), }, Dialer: &jimmtest.Dialer{ @@ -846,7 +846,7 @@ func TestWatcherRemoveDyingModelsOnStartup(t *testing.T) { w := &jimm.Watcher{ Pubsub: &testPublisher{}, - Database: db.Database{ + Database: &db.Database{ DB: jimmtest.PostgresDB(c, nil), }, Dialer: &jimmtest.Dialer{ @@ -935,7 +935,7 @@ func TestWatcherIgnoreDeltasForModelsFromIncorrectController(t *testing.T) { nextC := make(chan []jujuparams.Delta) w := &jimm.Watcher{ Pubsub: &testPublisher{}, - Database: db.Database{ + Database: &db.Database{ DB: jimmtest.PostgresDB(c, nil), }, Dialer: jimmtest.DialerMap{ diff --git a/internal/jimmhttp/rebac_admin/groups.go b/internal/jimmhttp/rebac_admin/groups.go index 2b388f2d2..f0a6e8508 100644 --- a/internal/jimmhttp/rebac_admin/groups.go +++ b/internal/jimmhttp/rebac_admin/groups.go @@ -37,7 +37,7 @@ func (s *groupsService) ListGroups(ctx context.Context, params *resources.GetGro if err != nil { return nil, err } - count, err := s.jimm.GetGroupManager().CountGroups(ctx, user) + count, err := s.jimm.GroupManager().CountGroups(ctx, user) if err != nil { return nil, err } @@ -46,7 +46,7 @@ func (s *groupsService) ListGroups(ctx context.Context, params *resources.GetGro if params.Filter != nil && *params.Filter != "" { match = *params.Filter } - groups, err := s.jimm.GetGroupManager().ListGroups(ctx, user, pagination, match) + groups, err := s.jimm.GroupManager().ListGroups(ctx, user, pagination, match) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func (s *groupsService) CreateGroup(ctx context.Context, group *resources.Group) if err != nil { return nil, err } - groupInfo, err := s.jimm.GetGroupManager().AddGroup(ctx, user, group.Name) + groupInfo, err := s.jimm.GroupManager().AddGroup(ctx, user, group.Name) if err != nil { return nil, err } @@ -86,7 +86,7 @@ func (s *groupsService) GetGroup(ctx context.Context, groupId string) (*resource if err != nil { return nil, err } - group, err := s.jimm.GetGroupManager().GetGroupByUUID(ctx, user, groupId) + group, err := s.jimm.GroupManager().GetGroupByUUID(ctx, user, groupId) if err != nil { if errors.ErrorCode(err) == errors.CodeNotFound { return nil, v1.NewNotFoundError("failed to find group") @@ -105,14 +105,14 @@ func (s *groupsService) UpdateGroup(ctx context.Context, group *resources.Group) if group.Id == nil { return nil, v1.NewValidationError("missing group ID") } - existingGroup, err := s.jimm.GetGroupManager().GetGroupByUUID(ctx, user, *group.Id) + existingGroup, err := s.jimm.GroupManager().GetGroupByUUID(ctx, user, *group.Id) if err != nil { if errors.ErrorCode(err) == errors.CodeNotFound { return nil, v1.NewNotFoundError("failed to find group") } return nil, err } - err = s.jimm.GetGroupManager().RenameGroup(ctx, user, existingGroup.Name, group.Name) + err = s.jimm.GroupManager().RenameGroup(ctx, user, existingGroup.Name, group.Name) if err != nil { return nil, err } @@ -128,14 +128,14 @@ func (s *groupsService) DeleteGroup(ctx context.Context, groupId string) (bool, if err != nil { return false, err } - existingGroup, err := s.jimm.GetGroupManager().GetGroupByUUID(ctx, user, groupId) + existingGroup, err := s.jimm.GroupManager().GetGroupByUUID(ctx, user, groupId) if err != nil { if errors.ErrorCode(err) == errors.CodeNotFound { return false, nil } return false, err } - err = s.jimm.GetGroupManager().RemoveGroup(ctx, user, existingGroup.Name) + err = s.jimm.GroupManager().RemoveGroup(ctx, user, existingGroup.Name) if err != nil { return false, err } @@ -153,7 +153,7 @@ func (s *groupsService) GetGroupIdentities(ctx context.Context, groupId string, } filter := utils.CreateTokenPaginationFilter(params.Size, params.NextToken, params.NextPageToken) groupTag := jimmnames.NewGroupTag(groupId) - _, err = s.jimm.GetGroupManager().GetGroupByUUID(ctx, user, groupId) + _, err = s.jimm.GroupManager().GetGroupByUUID(ctx, user, groupId) if err != nil { if errors.ErrorCode(err) == errors.CodeNotFound { return nil, v1.NewNotFoundError("group not found") @@ -248,7 +248,7 @@ func (s *groupsService) GetGroupRoles(ctx context.Context, groupId string, param filter := utils.CreateTokenPaginationFilter(params.Size, params.NextToken, params.NextPageToken) groupTag := jimmnames.NewGroupTag(groupId) - _, err = s.jimm.GetGroupManager().GetGroupByUUID(ctx, user, groupId) + _, err = s.jimm.GroupManager().GetGroupByUUID(ctx, user, groupId) if err != nil { if errors.ErrorCode(err) == errors.CodeNotFound { return nil, v1.NewNotFoundError("group not found") @@ -269,7 +269,7 @@ func (s *groupsService) GetGroupRoles(ctx context.Context, groupId string, param data := make([]resources.Role, 0, len(roles)) for _, role := range roles { roleUUID := role.Target.ID - roleEntry, err := s.jimm.GetRoleManager().GetRoleByUUID(ctx, user, roleUUID) + roleEntry, err := s.jimm.RoleManager().GetRoleByUUID(ctx, user, roleUUID) if err != nil { // If a role does not exist in the database but a linger tuple exists, drop the role from the results. if errors.ErrorCode(err) == errors.CodeNotFound { diff --git a/internal/jimmhttp/rebac_admin/groups_integration_test.go b/internal/jimmhttp/rebac_admin/groups_integration_test.go index 4a8fd7994..d73340a2d 100644 --- a/internal/jimmhttp/rebac_admin/groups_integration_test.go +++ b/internal/jimmhttp/rebac_admin/groups_integration_test.go @@ -33,7 +33,7 @@ var _ = gc.Suite(&rebacAdminSuite{}) func (s rebacAdminSuite) TestListGroupsWithFilterIntegration(c *gc.C) { ctx := context.Background() for i := range 10 { - _, err := s.JIMM.GroupManager.AddGroup(ctx, s.AdminUser, fmt.Sprintf("test-group-filter-%d", i)) + _, err := s.JIMM.GroupManager().AddGroup(ctx, s.AdminUser, fmt.Sprintf("test-group-filter-%d", i)) c.Assert(err, gc.IsNil) } @@ -63,7 +63,7 @@ func (s rebacAdminSuite) TestListGroupsWithFilterIntegration(c *gc.C) { func (s rebacAdminSuite) TestGetGroupIdentitiesIntegration(c *gc.C) { ctx := context.Background() - group, err := s.JIMM.GroupManager.AddGroup(ctx, s.AdminUser, "test-group") + group, err := s.JIMM.GroupManager().AddGroup(ctx, s.AdminUser, "test-group") c.Assert(err, gc.IsNil) tuple := openfga.Tuple{ Relation: ofganames.MemberRelation, @@ -111,7 +111,7 @@ func (s rebacAdminSuite) TestGetGroupIdentitiesIntegration(c *gc.C) { func (s rebacAdminSuite) TestPatchGroupIdentitiesIntegration(c *gc.C) { ctx := context.Background() - group, err := s.JIMM.GroupManager.AddGroup(ctx, s.AdminUser, "test-group") + group, err := s.JIMM.GroupManager().AddGroup(ctx, s.AdminUser, "test-group") c.Assert(err, gc.IsNil) tuple := openfga.Tuple{ Relation: ofganames.MemberRelation, @@ -214,7 +214,7 @@ func (s rebacAdminSuite) TestPatchGroupRolesIntegration(c *gc.C) { func (s rebacAdminSuite) TestGetGroupEntitlementsIntegration(c *gc.C) { ctx := context.Background() - group, err := s.JIMM.GroupManager.AddGroup(ctx, s.AdminUser, "test-group") + group, err := s.JIMM.GroupManager().AddGroup(ctx, s.AdminUser, "test-group") c.Assert(err, gc.IsNil) tuple := openfga.Tuple{ Object: ofganames.ConvertTagWithRelation(jimmnames.NewGroupTag(group.UUID), ofganames.MemberRelation), @@ -320,7 +320,7 @@ func (s rebacAdminSuite) TestPatchGroupEntitlementsIntegration(c *gc.C) { oldModels := []string{env.Models[0].UUID, env.Models[1].UUID} newModels := []string{env.Models[2].UUID, env.Models[3].UUID} - group, err := s.JIMM.GroupManager.AddGroup(ctx, s.AdminUser, "test-group") + group, err := s.JIMM.GroupManager().AddGroup(ctx, s.AdminUser, "test-group") c.Assert(err, gc.IsNil) tuple := openfga.Tuple{ Object: ofganames.ConvertTagWithRelation(jimmnames.NewGroupTag(group.UUID), ofganames.MemberRelation), diff --git a/internal/jimmhttp/rebac_admin/groups_test.go b/internal/jimmhttp/rebac_admin/groups_test.go index 1a471d0b0..11ec31e4e 100644 --- a/internal/jimmhttp/rebac_admin/groups_test.go +++ b/internal/jimmhttp/rebac_admin/groups_test.go @@ -34,7 +34,7 @@ func TestCreateGroup(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetGroupManager_: func() jimm.GroupManager { + GroupManager_: func() jimm.GroupManager { return &groupManager }, } @@ -67,7 +67,7 @@ func TestUpdateGroup(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetGroupManager_: func() jimm.GroupManager { + GroupManager_: func() jimm.GroupManager { return &groupManager }, } @@ -102,7 +102,7 @@ func TestListGroups(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetGroupManager_: func() jimm.GroupManager { + GroupManager_: func() jimm.GroupManager { return &groupManager }, } @@ -142,7 +142,7 @@ func TestDeleteGroup(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetGroupManager_: func() jimm.GroupManager { + GroupManager_: func() jimm.GroupManager { return &groupManager }, } @@ -174,7 +174,7 @@ func TestGetGroupIdentities(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetGroupManager_: func() jimm.GroupManager { + GroupManager_: func() jimm.GroupManager { return &groupManager }, RelationService: mocks.RelationService{ @@ -279,10 +279,10 @@ func TestGetGroupRoles(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetRoleManager_: func() jimm.RoleManager { + RoleManager_: func() jimm.RoleManager { return roleManager }, - GetGroupManager_: func() jimm.GroupManager { + GroupManager_: func() jimm.GroupManager { return &groupManager }, RelationService: mocks.RelationService{ diff --git a/internal/jimmhttp/rebac_admin/identities.go b/internal/jimmhttp/rebac_admin/identities.go index 75307ba35..c01acc363 100644 --- a/internal/jimmhttp/rebac_admin/identities.go +++ b/internal/jimmhttp/rebac_admin/identities.go @@ -120,7 +120,7 @@ func (s *identitiesService) GetIdentityRoles(ctx context.Context, identityId str roles := make([]resources.Role, 0, len(tuples)) for _, t := range tuples { - dbRole, err := s.jimm.GetRoleManager().GetRoleByUUID(ctx, user, t.Target.ID) + dbRole, err := s.jimm.RoleManager().GetRoleByUUID(ctx, user, t.Target.ID) if err != nil { // Handle the case where the role was removed from the DB but a lingering OpenFGA tuple still exists. // Don't return an error as that would prevent a user from viewing their groups, instead drop the role from the result. @@ -216,7 +216,7 @@ func (s *identitiesService) GetIdentityGroups(ctx context.Context, identityId st groups := make([]resources.Group, 0, len(tuples)) for _, t := range tuples { - dbGroup, err := s.jimm.GetGroupManager().GetGroupByUUID(ctx, user, t.Target.ID) + dbGroup, err := s.jimm.GroupManager().GetGroupByUUID(ctx, user, t.Target.ID) if err != nil { // Handle the case where the group was removed from the DB but a lingering OpenFGA tuple still exists. // Don't return an error as that would prevent a user from viewing their groups, instead drop the group from the result. diff --git a/internal/jimmhttp/rebac_admin/identities_test.go b/internal/jimmhttp/rebac_admin/identities_test.go index 4b4c067fa..13bf66a6e 100644 --- a/internal/jimmhttp/rebac_admin/identities_test.go +++ b/internal/jimmhttp/rebac_admin/identities_test.go @@ -169,7 +169,7 @@ func TestGetIdentityGroups(t *testing.T) { return []openfga.Tuple{testTuple}, "continuation-token", listTuplesErr }, }, - GetGroupManager_: func() jimm.GroupManager { + GroupManager_: func() jimm.GroupManager { return &groupManager }, } @@ -269,7 +269,7 @@ func TestGetIdentityRoles(t *testing.T) { return []openfga.Tuple{testTuple}, "continuation-token", listTuplesErr }, }, - GetRoleManager_: func() jimm.RoleManager { + RoleManager_: func() jimm.RoleManager { return roleManager }, } diff --git a/internal/jimmhttp/rebac_admin/roles.go b/internal/jimmhttp/rebac_admin/roles.go index 34303c53b..94349eb6a 100644 --- a/internal/jimmhttp/rebac_admin/roles.go +++ b/internal/jimmhttp/rebac_admin/roles.go @@ -35,7 +35,7 @@ func (s *rolesService) ListRoles(ctx context.Context, params *resources.GetRoles if err != nil { return nil, err } - count, err := s.jimm.GetRoleManager().CountRoles(ctx, user) + count, err := s.jimm.RoleManager().CountRoles(ctx, user) if err != nil { return nil, err } @@ -44,7 +44,7 @@ func (s *rolesService) ListRoles(ctx context.Context, params *resources.GetRoles if params.Filter != nil && *params.Filter != "" { match = *params.Filter } - roles, err := s.jimm.GetRoleManager().ListRoles(ctx, user, pagination, match) + roles, err := s.jimm.RoleManager().ListRoles(ctx, user, pagination, match) if err != nil { return nil, err } @@ -71,7 +71,7 @@ func (s *rolesService) CreateRole(ctx context.Context, role *resources.Role) (*r if err != nil { return nil, err } - roleInfo, err := s.jimm.GetRoleManager().AddRole(ctx, user, role.Name) + roleInfo, err := s.jimm.RoleManager().AddRole(ctx, user, role.Name) if err != nil { return nil, err } @@ -84,7 +84,7 @@ func (s *rolesService) GetRole(ctx context.Context, roleId string) (*resources.R if err != nil { return nil, err } - role, err := s.jimm.GetRoleManager().GetRoleByUUID(ctx, user, roleId) + role, err := s.jimm.RoleManager().GetRoleByUUID(ctx, user, roleId) if err != nil { if errors.ErrorCode(err) == errors.CodeNotFound { return nil, v1.NewNotFoundError("failed to find role") @@ -103,14 +103,14 @@ func (s *rolesService) UpdateRole(ctx context.Context, role *resources.Role) (*r if role.Id == nil { return nil, v1.NewValidationError("missing role ID") } - existingRole, err := s.jimm.GetRoleManager().GetRoleByUUID(ctx, user, *role.Id) + existingRole, err := s.jimm.RoleManager().GetRoleByUUID(ctx, user, *role.Id) if err != nil { if errors.ErrorCode(err) == errors.CodeNotFound { return nil, v1.NewNotFoundError("failed to find role") } return nil, err } - err = s.jimm.GetRoleManager().RenameRole(ctx, user, existingRole.Name, role.Name) + err = s.jimm.RoleManager().RenameRole(ctx, user, existingRole.Name, role.Name) if err != nil { return nil, err } @@ -126,14 +126,14 @@ func (s *rolesService) DeleteRole(ctx context.Context, roleId string) (bool, err if err != nil { return false, err } - existingRole, err := s.jimm.GetRoleManager().GetRoleByUUID(ctx, user, roleId) + existingRole, err := s.jimm.RoleManager().GetRoleByUUID(ctx, user, roleId) if err != nil { if errors.ErrorCode(err) == errors.CodeNotFound { return false, nil } return false, err } - err = s.jimm.GetRoleManager().RemoveRole(ctx, user, existingRole.Name) + err = s.jimm.RoleManager().RemoveRole(ctx, user, existingRole.Name) if err != nil { return false, err } diff --git a/internal/jimmhttp/rebac_admin/roles_integration_test.go b/internal/jimmhttp/rebac_admin/roles_integration_test.go index d7920aaba..73038248b 100644 --- a/internal/jimmhttp/rebac_admin/roles_integration_test.go +++ b/internal/jimmhttp/rebac_admin/roles_integration_test.go @@ -33,7 +33,7 @@ var _ = gc.Suite(&roleSuite{}) func (s roleSuite) TestListRolesWithFilterIntegration(c *gc.C) { ctx := context.Background() for i := range 10 { - _, err := s.JIMM.RoleManager.AddRole(ctx, s.AdminUser, fmt.Sprintf("test-role-filter-%d", i)) + _, err := s.JIMM.RoleManager().AddRole(ctx, s.AdminUser, fmt.Sprintf("test-role-filter-%d", i)) c.Assert(err, gc.IsNil) } @@ -63,7 +63,7 @@ func (s roleSuite) TestListRolesWithFilterIntegration(c *gc.C) { func (s roleSuite) TestGetRoleEntitlementsIntegration(c *gc.C) { ctx := context.Background() - role, err := s.JIMM.RoleManager.AddRole(ctx, s.AdminUser, "test-role") + role, err := s.JIMM.RoleManager().AddRole(ctx, s.AdminUser, "test-role") c.Assert(err, gc.IsNil) tuple := openfga.Tuple{ Object: ofganames.ConvertTagWithRelation(jimmnames.NewRoleTag(role.UUID), ofganames.AssigneeRelation), @@ -169,7 +169,7 @@ func (s roleSuite) TestPatchRoleEntitlementsIntegration(c *gc.C) { oldModels := []string{env.Models[0].UUID, env.Models[1].UUID} newModels := []string{env.Models[2].UUID, env.Models[3].UUID} - role, err := s.JIMM.RoleManager.AddRole(ctx, s.AdminUser, "test-role") + role, err := s.JIMM.RoleManager().AddRole(ctx, s.AdminUser, "test-role") c.Assert(err, gc.IsNil) tuple := openfga.Tuple{ Object: ofganames.ConvertTagWithRelation(jimmnames.NewRoleTag(role.UUID), ofganames.AssigneeRelation), diff --git a/internal/jimmhttp/rebac_admin/roles_test.go b/internal/jimmhttp/rebac_admin/roles_test.go index 5848876b7..bdffc1a34 100644 --- a/internal/jimmhttp/rebac_admin/roles_test.go +++ b/internal/jimmhttp/rebac_admin/roles_test.go @@ -32,7 +32,7 @@ func TestCreateRole(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetRoleManager_: func() jimm.RoleManager { + RoleManager_: func() jimm.RoleManager { return roleManager }, } @@ -65,7 +65,7 @@ func TestUpdateRole(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetRoleManager_: func() jimm.RoleManager { + RoleManager_: func() jimm.RoleManager { return roleManager }, } @@ -100,7 +100,7 @@ func TestListRoles(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetRoleManager_: func() jimm.RoleManager { + RoleManager_: func() jimm.RoleManager { return roleManager }, } @@ -128,7 +128,7 @@ func TestListRoles(t *testing.T) { func TestDeleteRole(t *testing.T) { c := qt.New(t) var deleteErr error - RoleManager := mocks.RoleManager{ + roleManager := mocks.RoleManager{ GetRoleByUUID_: func(ctx context.Context, user *openfga.User, uuid string) (*dbmodel.RoleEntry, error) { return &dbmodel.RoleEntry{UUID: uuid, Name: "test-role"}, nil }, @@ -140,8 +140,8 @@ func TestDeleteRole(t *testing.T) { }, } jimm := jimmtest.JIMM{ - GetRoleManager_: func() jimm.RoleManager { - return RoleManager + RoleManager_: func() jimm.RoleManager { + return roleManager }, } user := openfga.User{} diff --git a/internal/jujuapi/access_control.go b/internal/jujuapi/access_control.go index 37fa9b4e8..90272701c 100644 --- a/internal/jujuapi/access_control.go +++ b/internal/jujuapi/access_control.go @@ -33,7 +33,7 @@ func (r *controllerRoot) AddGroup(ctx context.Context, req apiparams.AddGroupReq return resp, errors.E(op, errors.CodeBadRequest, "invalid group name") } - groupEntry, err := r.jimm.GetGroupManager().AddGroup(ctx, r.user, req.Name) + groupEntry, err := r.jimm.GroupManager().AddGroup(ctx, r.user, req.Name) if err != nil { zapctx.Error(ctx, "failed to add group", zaputil.Error(err)) return resp, errors.E(op, err) @@ -58,9 +58,9 @@ func (r *controllerRoot) GetGroup(ctx context.Context, req apiparams.GetGroupReq case req.UUID != "" && req.Name != "": return apiparams.Group{}, errors.E(op, errors.CodeBadRequest, "only one of UUID or Name should be provided") case req.UUID != "": - groupEntry, err = r.jimm.GetGroupManager().GetGroupByUUID(ctx, r.user, req.UUID) + groupEntry, err = r.jimm.GroupManager().GetGroupByUUID(ctx, r.user, req.UUID) case req.Name != "": - groupEntry, err = r.jimm.GetGroupManager().GetGroupByName(ctx, r.user, req.Name) + groupEntry, err = r.jimm.GroupManager().GetGroupByName(ctx, r.user, req.Name) default: return apiparams.Group{}, errors.E(op, errors.CodeBadRequest, "no UUID or Name provided") } @@ -85,7 +85,7 @@ func (r *controllerRoot) RenameGroup(ctx context.Context, req apiparams.RenameGr return errors.E(op, errors.CodeBadRequest, "invalid group name") } - if err := r.jimm.GetGroupManager().RenameGroup(ctx, r.user, req.Name, req.NewName); err != nil { + if err := r.jimm.GroupManager().RenameGroup(ctx, r.user, req.Name, req.NewName); err != nil { zapctx.Error(ctx, "failed to rename group", zaputil.Error(err)) return errors.E(op, err) } @@ -96,7 +96,7 @@ func (r *controllerRoot) RenameGroup(ctx context.Context, req apiparams.RenameGr func (r *controllerRoot) RemoveGroup(ctx context.Context, req apiparams.RemoveGroupRequest) error { const op = errors.Op("jujuapi.RemoveGroup") - if err := r.jimm.GetGroupManager().RemoveGroup(ctx, r.user, req.Name); err != nil { + if err := r.jimm.GroupManager().RemoveGroup(ctx, r.user, req.Name); err != nil { zapctx.Error(ctx, "failed to remove group", zaputil.Error(err)) return errors.E(op, err) } @@ -108,7 +108,7 @@ func (r *controllerRoot) ListGroups(ctx context.Context, req apiparams.ListGroup const op = errors.Op("jujuapi.ListGroups") pagination := pagination.NewOffsetFilter(req.Limit, req.Offset) - groups, err := r.jimm.GetGroupManager().ListGroups(ctx, r.user, pagination, "") + groups, err := r.jimm.GroupManager().ListGroups(ctx, r.user, pagination, "") if err != nil { return apiparams.ListGroupResponse{}, errors.E(op, err) } diff --git a/internal/jujuapi/admin_test.go b/internal/jujuapi/admin_test.go index dce818039..692151a1b 100644 --- a/internal/jujuapi/admin_test.go +++ b/internal/jujuapi/admin_test.go @@ -58,7 +58,7 @@ func (s *adminSuite) SetUpTest(c *gc.C) { ClientSecret: "SwjDofnbDzJDm9iyfUhEp67FfUFMY8L4", Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, SessionTokenExpiry: time.Hour, - Store: &s.JIMM.Database, + Store: s.JIMM.Database, SessionStore: sessionStore, SessionCookieMaxAge: 60, JWTSessionKey: "test-secret", @@ -124,7 +124,7 @@ func testBrowserLogin(c *gc.C, s *adminSuite, username, password, expectedEmail, defer sessionStore.Close() cookie, err := jimmtest.RunBrowserLogin( - &s.JIMM.Database, + s.JIMM.Database, sessionStore, username, password, diff --git a/internal/jujuapi/controllerroot.go b/internal/jujuapi/controllerroot.go index 7328e8f75..acaf97daf 100644 --- a/internal/jujuapi/controllerroot.go +++ b/internal/jujuapi/controllerroot.go @@ -50,8 +50,8 @@ type JIMM interface { GetCloudCredential(ctx context.Context, user *openfga.User, tag names.CloudCredentialTag) (*dbmodel.CloudCredential, error) GetCloudCredentialAttributes(ctx context.Context, u *openfga.User, cred *dbmodel.CloudCredential, hidden bool) (attrs map[string]string, redacted []string, err error) GetCredentialStore() credentials.CredentialStore - GetRoleManager() jimm.RoleManager - GetGroupManager() jimm.GroupManager + RoleManager() jimm.RoleManager + GroupManager() jimm.GroupManager GetJimmControllerAccess(ctx context.Context, user *openfga.User, tag names.UserTag) (string, error) // FetchIdentity finds the user in jimm or returns a not-found error FetchIdentity(ctx context.Context, username string) (*openfga.User, error) diff --git a/internal/jujuapi/role.go b/internal/jujuapi/role.go index a5cef25a6..e1b4d1177 100644 --- a/internal/jujuapi/role.go +++ b/internal/jujuapi/role.go @@ -25,7 +25,7 @@ func (r *controllerRoot) AddRole(ctx context.Context, req apiparams.AddRoleReque return resp, errors.E(op, errors.CodeBadRequest, "invalid role name") } - roleEntry, err := r.jimm.GetRoleManager().AddRole(ctx, r.user, req.Name) + roleEntry, err := r.jimm.RoleManager().AddRole(ctx, r.user, req.Name) if err != nil { zapctx.Error(ctx, "failed to add role", zaputil.Error(err)) return resp, errors.E(op, err) @@ -52,9 +52,9 @@ func (r *controllerRoot) GetRole(ctx context.Context, req apiparams.GetRoleReque case req.Name != "" && !jimmnames.IsValidRoleName(req.Name): return apiparams.Role{}, errors.E(op, errors.CodeBadRequest, "invalid role name") case req.UUID != "": - roleEntry, err = r.jimm.GetRoleManager().GetRoleByUUID(ctx, r.user, req.UUID) + roleEntry, err = r.jimm.RoleManager().GetRoleByUUID(ctx, r.user, req.UUID) case req.Name != "": - roleEntry, err = r.jimm.GetRoleManager().GetRoleByName(ctx, r.user, req.Name) + roleEntry, err = r.jimm.RoleManager().GetRoleByName(ctx, r.user, req.Name) default: return apiparams.Role{}, errors.E(op, errors.CodeBadRequest, "no UUID or Name provided") } @@ -79,7 +79,7 @@ func (r *controllerRoot) RenameRole(ctx context.Context, req apiparams.RenameRol return errors.E(op, errors.CodeBadRequest, "invalid role name") } - if err := r.jimm.GetRoleManager().RenameRole(ctx, r.user, req.Name, req.NewName); err != nil { + if err := r.jimm.RoleManager().RenameRole(ctx, r.user, req.Name, req.NewName); err != nil { zapctx.Error(ctx, "failed to rename role", zaputil.Error(err)) return errors.E(op, err) } @@ -94,7 +94,7 @@ func (r *controllerRoot) RemoveRole(ctx context.Context, req apiparams.RemoveRol return errors.E(op, errors.CodeBadRequest, "invalid role name") } - if err := r.jimm.GetRoleManager().RemoveRole(ctx, r.user, req.Name); err != nil { + if err := r.jimm.RoleManager().RemoveRole(ctx, r.user, req.Name); err != nil { zapctx.Error(ctx, "failed to remove role", zaputil.Error(err)) return errors.E(op, err) } @@ -106,7 +106,7 @@ func (r *controllerRoot) ListRoles(ctx context.Context, req apiparams.ListRolesR const op = errors.Op("jujuapi.ListRoles") pagination := pagination.NewOffsetFilter(req.Limit, req.Offset) - roles, err := r.jimm.GetRoleManager().ListRoles(ctx, r.user, pagination, "") + roles, err := r.jimm.RoleManager().ListRoles(ctx, r.user, pagination, "") if err != nil { return apiparams.ListRoleResponse{}, errors.E(op, err) } diff --git a/internal/jujuapi/websocket.go b/internal/jujuapi/websocket.go index 7da197c96..cf8f278d8 100644 --- a/internal/jujuapi/websocket.go +++ b/internal/jujuapi/websocket.go @@ -172,7 +172,7 @@ func modelInfoFromPath(path string) (uuid string, finalPath string, err error) { // We act as a proxier, handling auth on requests before forwarding the // requests to the appropriate Juju controller. func (s apiProxier) ServeWS(ctx context.Context, clientConn *websocket.Conn) { - jwtGenerator := jimm.NewJWTGenerator(&s.jimm.Database, s.jimm, s.jimm.JWTService) + jwtGenerator := jimm.NewJWTGenerator(s.jimm.Database, s.jimm, s.jimm.JWTService) connectionFunc := controllerConnectionFunc(s, &jwtGenerator) zapctx.Debug(ctx, "Starting proxier") auditLogger := s.jimm.AddAuditLogEntry diff --git a/internal/jujuclient/dial.go b/internal/jujuclient/dial.go index 4ea3d8698..6a2c35f68 100644 --- a/internal/jujuclient/dial.go +++ b/internal/jujuclient/dial.go @@ -103,7 +103,7 @@ func (d *Dialer) Dial(ctx context.Context, ctl *dbmodel.Controller, modelTag nam var res jujuparams.LoginResult if err := client.Call(ctx, "Admin", 3, "", "Login", loginRequest, &res); err != nil { client.Close() - return nil, errors.E(op, errors.CodeConnectionFailed, "authentication failed", err) + return nil, errors.E(op, errors.CodeConnectionFailed, err) } ct, err := names.ParseControllerTag(res.ControllerTag) diff --git a/internal/testutils/cmdtest/jimmsuite.go b/internal/testutils/cmdtest/jimmsuite.go index d6ed26faf..349b043e4 100644 --- a/internal/testutils/cmdtest/jimmsuite.go +++ b/internal/testutils/cmdtest/jimmsuite.go @@ -99,6 +99,7 @@ func (s *JimmCmdSuite) SetUpTest(c *gc.C) { srv, err := service.NewService(ctx, s.Params) c.Assert(err, gc.Equals, nil) + s.Service = srv s.JIMM = srv.JIMM() s.HTTP.Config = &http.Server{Handler: srv, ReadHeaderTimeout: time.Second * 5} diff --git a/internal/testutils/jimmtest/env.go b/internal/testutils/jimmtest/env.go index 65b00e8e2..52c0f315e 100644 --- a/internal/testutils/jimmtest/env.go +++ b/internal/testutils/jimmtest/env.go @@ -132,7 +132,7 @@ func (e *Environment) User(name string) *User { } // addUserRelations adds permissions the user should have. -func (u User) addUserRelations(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { +func (u User) addUserRelations(c *qt.C, jimmTag names.ControllerTag, db *db.Database, client *openfga.OFGAClient) { if u.ControllerAccess == "superuser" { dbUser := u.DBObject(c, db) u := openfga.NewUser(&dbUser, client) @@ -142,7 +142,7 @@ func (u User) addUserRelations(c *qt.C, jimmTag names.ControllerTag, db db.Datab } // addCloudRelations adds permissions the cloud should have and adds permissions for users to the cloud. -func (cl Cloud) addCloudRelations(c *qt.C, db db.Database, client *openfga.OFGAClient) { +func (cl Cloud) addCloudRelations(c *qt.C, db *db.Database, client *openfga.OFGAClient) { for _, u := range cl.Users { dbUser := cl.env.User(u.User).DBObject(c, db) var relation openfga.Relation @@ -163,7 +163,7 @@ func (cl Cloud) addCloudRelations(c *qt.C, db db.Database, client *openfga.OFGAC } // addModelRelations adds permissions the model should have and adds permissions for users to the model. -func (m Model) addModelRelations(c *qt.C, db db.Database, client *openfga.OFGAClient) { +func (m Model) addModelRelations(c *qt.C, db *db.Database, client *openfga.OFGAClient) { owner := openfga.NewUser(&m.dbo.Owner, client) err := owner.SetModelAccess(context.Background(), m.dbo.ResourceTag(), ofganames.AdministratorRelation) c.Assert(err, qt.IsNil) @@ -207,7 +207,7 @@ func (ctl Controller) addControllerRelations(c *qt.C, client *openfga.OFGAClient c.Assert(err, qt.IsNil) } -func (e *Environment) addJIMMRelations(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { +func (e *Environment) addJIMMRelations(c *qt.C, jimmTag names.ControllerTag, db *db.Database, client *openfga.OFGAClient) { for _, user := range e.Users { user.addUserRelations(c, jimmTag, db, client) } @@ -226,13 +226,13 @@ func (e *Environment) addJIMMRelations(c *qt.C, jimmTag names.ControllerTag, db } } -func (e *Environment) PopulateDBAndPermissions(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { +func (e *Environment) PopulateDBAndPermissions(c *qt.C, jimmTag names.ControllerTag, db *db.Database, client *openfga.OFGAClient) { e.PopulateDB(c, db) c.Assert(client, qt.IsNotNil) e.addJIMMRelations(c, jimmTag, db, client) } -func (e *Environment) PopulateDB(c Tester, db db.Database) { +func (e *Environment) PopulateDB(c Tester, db *db.Database) { for i := range e.Users { e.Users[i].env = e e.Users[i].DBObject(c, db) @@ -279,7 +279,7 @@ type ApplicationOffer struct { dbo dbmodel.ApplicationOffer } -func (cd *ApplicationOffer) DBObject(c Tester, db db.Database) dbmodel.ApplicationOffer { +func (cd *ApplicationOffer) DBObject(c Tester, db *db.Database) dbmodel.ApplicationOffer { if cd.dbo.ID != 0 { return cd.dbo } @@ -307,7 +307,7 @@ type UserDefaults struct { dbo dbmodel.IdentityModelDefaults } -func (cd *UserDefaults) DBObject(c Tester, db db.Database) dbmodel.IdentityModelDefaults { +func (cd *UserDefaults) DBObject(c Tester, db *db.Database) dbmodel.IdentityModelDefaults { if cd.dbo.ID != 0 { return cd.dbo } @@ -334,7 +334,7 @@ type CloudDefaults struct { dbo dbmodel.CloudDefaults } -func (cd *CloudDefaults) DBObject(c Tester, db db.Database) dbmodel.CloudDefaults { +func (cd *CloudDefaults) DBObject(c Tester, db *db.Database) dbmodel.CloudDefaults { if cd.dbo.ID != 0 { return cd.dbo } @@ -372,7 +372,7 @@ type CloudRegion struct { // DBObject returns a database object for the specified cloud, suitable // for adding to the database. -func (cl *Cloud) DBObject(c Tester, db db.Database) dbmodel.Cloud { +func (cl *Cloud) DBObject(c Tester, db *db.Database) dbmodel.Cloud { if cl.dbo.ID != 0 { return cl.dbo } @@ -407,7 +407,7 @@ type CloudCredential struct { dbo dbmodel.CloudCredential } -func (cc *CloudCredential) DBObject(c Tester, db db.Database) dbmodel.CloudCredential { +func (cc *CloudCredential) DBObject(c Tester, db *db.Database) dbmodel.CloudCredential { if cc.dbo.ID != 0 { return cc.dbo } @@ -443,7 +443,7 @@ type Controller struct { dbo dbmodel.Controller } -func (ctl *Controller) DBObject(c Tester, db db.Database) dbmodel.Controller { +func (ctl *Controller) DBObject(c Tester, db *db.Database) dbmodel.Controller { if ctl.dbo.ID != 0 { return ctl.dbo } @@ -505,7 +505,7 @@ type Model struct { dbo dbmodel.Model } -func (m *Model) DBObject(c Tester, db db.Database) dbmodel.Model { +func (m *Model) DBObject(c Tester, db *db.Database) dbmodel.Model { if m.dbo.ID != 0 { return m.dbo } @@ -556,7 +556,7 @@ type User struct { dbo dbmodel.Identity } -func (u *User) DBObject(c Tester, db db.Database) dbmodel.Identity { +func (u *User) DBObject(c Tester, db *db.Database) dbmodel.Identity { if u.dbo.ID != 0 { return u.dbo } diff --git a/internal/testutils/jimmtest/fixture.go b/internal/testutils/jimmtest/fixture.go index 640f863a1..b9458b534 100644 --- a/internal/testutils/jimmtest/fixture.go +++ b/internal/testutils/jimmtest/fixture.go @@ -31,7 +31,7 @@ import ( // // TODO(ale8k): Make this an implicit thing on the JIMM suite per test & refactor the current state. // and make the suite argument an interface of the required calls we use here. -func CreateTestControllerEnvironment(ctx context.Context, c *qt.C, db db.Database) ( +func CreateTestControllerEnvironment(ctx context.Context, c *qt.C, db *db.Database) ( dbmodel.Identity, dbmodel.GroupEntry, dbmodel.Controller, diff --git a/internal/testutils/jimmtest/jimm.go b/internal/testutils/jimmtest/jimm.go new file mode 100644 index 000000000..71137eacc --- /dev/null +++ b/internal/testutils/jimmtest/jimm.go @@ -0,0 +1,98 @@ +// Copyright 2024 Canonical. + +package jimmtest + +import ( + "time" + + "github.com/google/uuid" + + "github.com/canonical/jimm/v3/internal/db" + "github.com/canonical/jimm/v3/internal/jimm" + "github.com/canonical/jimm/v3/internal/jimmjwx" + "github.com/canonical/jimm/v3/internal/pubsub" +) + +var now = (time.Time{}).UTC().Round(time.Millisecond) + +type Option func(j *jimm.JIMM) + +var ( + UnsetCredentialStore Option = func(j *jimm.JIMM) { + j.CredentialStore = nil + } +) + +func NewJIMM(t Tester, additionalParameters *jimm.Parameters, options ...Option) *jimm.JIMM { + + auth := NewMockOAuthAuthenticator(t, nil) + + p := jimm.Parameters{ + UUID: uuid.NewString(), + Dialer: &Dialer{}, + Pubsub: &pubsub.Hub{}, + JWKService: &jimmjwx.JWKSService{}, + JWTService: &jimmjwx.JWTService{}, + OAuthAuthenticator: &auth, + } + + if additionalParameters != nil { + if additionalParameters.UUID != "" { + p.UUID = additionalParameters.UUID + } + if additionalParameters.Dialer != nil { + p.Dialer = additionalParameters.Dialer + } + if additionalParameters.Database != nil { + p.Database = additionalParameters.Database + } + if additionalParameters.CredentialStore != nil { + p.CredentialStore = additionalParameters.CredentialStore + } + if additionalParameters.Pubsub != nil { + p.Pubsub = additionalParameters.Pubsub + } + if len(additionalParameters.ReservedCloudNames) > 0 { + p.ReservedCloudNames = append(p.ReservedCloudNames, additionalParameters.ReservedCloudNames...) + } + if additionalParameters.OpenFGAClient != nil { + p.OpenFGAClient = additionalParameters.OpenFGAClient + } + if additionalParameters.JWKService != nil { + p.JWKService = additionalParameters.JWKService + } + if additionalParameters.JWTService != nil { + p.JWTService = additionalParameters.JWTService + } + if additionalParameters.OAuthAuthenticator != nil { + p.OAuthAuthenticator = additionalParameters.OAuthAuthenticator + } + } + + if p.Database == nil { + p.Database = &db.Database{ + DB: PostgresDB(t, func() time.Time { return now }), + } + } + if p.CredentialStore == nil { + p.CredentialStore = p.Database + } + if p.OpenFGAClient == nil { + ofgaClient, _, _, err := SetupTestOFGAClient(t.Name()) + if err != nil { + t.Fatalf("setting up openfga client: %v", err) + } + p.OpenFGAClient = ofgaClient + } + + j, err := jimm.New(p) + if err != nil { + t.Fatalf("instantiating jimm: %v", err) + } + + for _, option := range options { + option(j) + } + + return j +} diff --git a/internal/testutils/jimmtest/jimm_mock.go b/internal/testutils/jimmtest/jimm_mock.go index e6b9f2f94..e59968d3d 100644 --- a/internal/testutils/jimmtest/jimm_mock.go +++ b/internal/testutils/jimmtest/jimm_mock.go @@ -40,7 +40,9 @@ type JIMM struct { Authenticate_ func(ctx context.Context, req *jujuparams.LoginRequest) (*openfga.User, error) CheckPermission_ func(ctx context.Context, user *openfga.User, cachedPerms map[string]string, desiredPerms map[string]interface{}) (map[string]string, error) CopyServiceAccountCredential_ func(ctx context.Context, u *openfga.User, svcAcc *openfga.User, cloudCredentialTag names.CloudCredentialTag) (names.CloudCredentialTag, []jujuparams.UpdateCredentialModelResult, error) + CountIdentities_ func(ctx context.Context, user *openfga.User) (int, error) DestroyOffer_ func(ctx context.Context, user *openfga.User, offerURL string, force bool) error + FetchIdentity_ func(ctx context.Context, username string) (*openfga.User, error) FindApplicationOffers_ func(ctx context.Context, user *openfga.User, filters ...jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) FindAuditEvents_ func(ctx context.Context, user *openfga.User, filter db.AuditLogFilter) ([]dbmodel.AuditLogEntry, error) ForEachCloud_ func(ctx context.Context, user *openfga.User, f func(*dbmodel.Cloud) error) error @@ -52,12 +54,7 @@ type JIMM struct { GetCloudCredential_ func(ctx context.Context, user *openfga.User, tag names.CloudCredentialTag) (*dbmodel.CloudCredential, error) GetCloudCredentialAttributes_ func(ctx context.Context, u *openfga.User, cred *dbmodel.CloudCredential, hidden bool) (attrs map[string]string, redacted []string, err error) GetCredentialStore_ func() jimmcreds.CredentialStore - GetGroupManager_ func() jimm.GroupManager - GetRoleManager_ func() jimm.RoleManager GetJimmControllerAccess_ func(ctx context.Context, user *openfga.User, tag names.UserTag) (string, error) - FetchIdentity_ func(ctx context.Context, username string) (*openfga.User, error) - CountIdentities_ func(ctx context.Context, user *openfga.User) (int, error) - ListIdentities_ func(ctx context.Context, user *openfga.User, pagination pagination.LimitOffsetPagination, match string) ([]openfga.User, error) GetUserCloudAccess_ func(ctx context.Context, user *openfga.User, cloud names.CloudTag) (string, error) GetUserControllerAccess_ func(ctx context.Context, user *openfga.User, controller names.ControllerTag) (string, error) GetUserModelAccess_ func(ctx context.Context, user *openfga.User, model names.ModelTag) (string, error) @@ -66,9 +63,11 @@ type JIMM struct { GrantModelAccess_ func(ctx context.Context, user *openfga.User, mt names.ModelTag, ut names.UserTag, access jujuparams.UserAccessPermission) error GrantOfferAccess_ func(ctx context.Context, u *openfga.User, offerURL string, ut names.UserTag, access jujuparams.OfferAccessPermission) error GrantServiceAccountAccess_ func(ctx context.Context, u *openfga.User, svcAccTag jimmnames.ServiceAccountTag, entities []string) error - InitiateMigration_ func(ctx context.Context, user *openfga.User, spec jujuparams.MigrationSpec) (jujuparams.InitiateMigrationResult, error) + GroupManager_ func() jimm.GroupManager InitiateInternalMigration_ func(ctx context.Context, user *openfga.User, modelTag names.ModelTag, targetController string) (jujuparams.InitiateMigrationResult, error) + InitiateMigration_ func(ctx context.Context, user *openfga.User, spec jujuparams.MigrationSpec) (jujuparams.InitiateMigrationResult, error) ListApplicationOffers_ func(ctx context.Context, user *openfga.User, filters ...jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) + ListIdentities_ func(ctx context.Context, user *openfga.User, pagination pagination.LimitOffsetPagination, match string) ([]openfga.User, error) ListResources_ func(ctx context.Context, user *openfga.User, filter pagination.LimitOffsetPagination, namePrefixFilter, typeFilter string) ([]db.Resource, error) Offer_ func(ctx context.Context, user *openfga.User, offer jimm.AddApplicationOfferParams) error PubSubHub_ func() *pubsub.Hub @@ -81,6 +80,7 @@ type JIMM struct { RevokeCloudCredential_ func(ctx context.Context, user *dbmodel.Identity, tag names.CloudCredentialTag, force bool) error RevokeModelAccess_ func(ctx context.Context, user *openfga.User, mt names.ModelTag, ut names.UserTag, access jujuparams.UserAccessPermission) error RevokeOfferAccess_ func(ctx context.Context, user *openfga.User, offerURL string, ut names.UserTag, access jujuparams.OfferAccessPermission) (err error) + RoleManager_ func() jimm.RoleManager SetIdentityModelDefaults_ func(ctx context.Context, user *dbmodel.Identity, configs map[string]interface{}) error ToJAASTag_ func(ctx context.Context, tag *ofganames.Tag, resolveUUIDs bool) (string, error) UpdateApplicationOffer_ func(ctx context.Context, controller *dbmodel.Controller, offerUUID string, removed bool) error @@ -211,18 +211,18 @@ func (j *JIMM) GetCredentialStore() jimmcreds.CredentialStore { return j.GetCredentialStore_() } -func (j *JIMM) GetRoleManager() jimm.RoleManager { - if j.GetRoleManager_ == nil { +func (j *JIMM) RoleManager() jimm.RoleManager { + if j.RoleManager_ == nil { return nil } - return j.GetRoleManager_() + return j.RoleManager_() } -func (j *JIMM) GetGroupManager() jimm.GroupManager { - if j.GetGroupManager_ == nil { +func (j *JIMM) GroupManager() jimm.GroupManager { + if j.GroupManager_ == nil { return nil } - return j.GetGroupManager_() + return j.GroupManager_() } func (j *JIMM) GetJimmControllerAccess(ctx context.Context, user *openfga.User, tag names.UserTag) (string, error) { diff --git a/internal/testutils/jimmtest/suite.go b/internal/testutils/jimmtest/suite.go index 732296e0b..a2713c7bf 100644 --- a/internal/testutils/jimmtest/suite.go +++ b/internal/testutils/jimmtest/suite.go @@ -23,8 +23,6 @@ import ( "github.com/canonical/jimm/v3/internal/dbmodel" "github.com/canonical/jimm/v3/internal/discharger" "github.com/canonical/jimm/v3/internal/jimm" - "github.com/canonical/jimm/v3/internal/jimm/group" - "github.com/canonical/jimm/v3/internal/jimm/role" "github.com/canonical/jimm/v3/internal/jimmhttp" "github.com/canonical/jimm/v3/internal/jimmjwx" "github.com/canonical/jimm/v3/internal/jujuclient" @@ -39,6 +37,7 @@ const ControllerUUID = "c1991ce8-96c2-497d-8e2a-e0cc42ca3aca" // A GocheckTester adapts a gc.C to the Tester interface. type GocheckTester struct { *gc.C + AddCleanup func(func()) } // Name implements Tester.Name. @@ -47,7 +46,11 @@ func (t GocheckTester) Name() string { } func (t GocheckTester) Cleanup(f func()) { - t.C.Logf("warning: gocheck does not support Cleanup functions; make sure you're using suite's tear-down method") + if t.AddCleanup != nil { + t.AddCleanup(f) + } else { + t.C.Logf("warning: gocheck does not support Cleanup functions; make sure you're using suite's tear-down method") + } } // A JIMMSuite is a suite that initialises a JIMM. @@ -62,63 +65,53 @@ type JIMMSuite struct { COFGAClient *cofga.Client COFGAParams *cofga.OpenFGAParams - Server *httptest.Server - cancel context.CancelFunc - deviceFlowChan chan string - databaseName string + Server *httptest.Server + cancel context.CancelFunc + deviceFlowChan chan string + databaseName string + databaseCleanup []func() } func (s *JIMMSuite) SetUpTest(c *gc.C) { var err error + + ctx, cancel := context.WithCancel(context.Background()) + s.cancel = cancel + + // Setup OpenFGA. s.OFGAClient, s.COFGAClient, s.COFGAParams, err = SetupTestOFGAClient(c.TestName()) c.Assert(err, gc.IsNil) - pgdb, databaseName := PostgresDBWithDbName(GocheckTester{c}, nil) - s.databaseName = databaseName - // Setup OpenFGA. - s.JIMM = &jimm.JIMM{ - Database: db.Database{ - DB: pgdb, + gct := &GocheckTester{ + C: c, + AddCleanup: func(f func()) { + s.databaseCleanup = append(s.databaseCleanup, f) }, - CredentialStore: NewInMemoryCredentialStore(), - Pubsub: &pubsub.Hub{MaxConcurrency: 10}, - UUID: ControllerUUID, - OpenFGAClient: s.OFGAClient, } - roleManager, err := role.NewRoleManager(&s.JIMM.Database, s.OFGAClient) - c.Assert(err, gc.IsNil) - s.JIMM.RoleManager = roleManager - - groupManager, err := group.NewGroupManager(&s.JIMM.Database, s.OFGAClient) - c.Assert(err, gc.IsNil) - s.JIMM.GroupManager = groupManager - - ctx, cancel := context.WithCancel(context.Background()) - s.cancel = cancel + pgdb, databaseName := PostgresDBWithDbName(gct, nil) + s.databaseName = databaseName s.deviceFlowChan = make(chan string, 1) authenticator := NewMockOAuthAuthenticator(c, s.deviceFlowChan) - s.JIMM.OAuthAuthenticator = &authenticator - err = s.JIMM.Database.Migrate(ctx, false) + database := &db.Database{ + DB: pgdb, + } + err = database.Migrate(ctx, false) c.Assert(err, gc.Equals, nil) alice, err := dbmodel.NewIdentity("alice@canonical.com") c.Assert(err, gc.IsNil) alice.LastLogin = db.Now() - err = s.JIMM.Database.GetIdentity(ctx, alice) + err = database.GetIdentity(ctx, alice) c.Assert(err, gc.Equals, nil) s.AdminUser = openfga.NewUser(alice, s.OFGAClient) s.AdminUser.JimmAdmin = true - err = s.AdminUser.SetControllerAccess(ctx, s.JIMM.ResourceTag(), ofganames.AdministratorRelation) - c.Assert(err, gc.Equals, nil) - // add jimmtest.DefaultControllerUUID as a controller to JIMM - err = s.OFGAClient.AddController(ctx, s.JIMM.ResourceTag(), names.NewControllerTag("982b16d9-a945-4762-b684-fd4fd885aa10")) - c.Assert(err, gc.Equals, nil) + credentialStore := NewInMemoryCredentialStore() mux := chi.NewRouter() mountHandler := func(path string, h jimmhttp.JIMMHttpHandler) { @@ -127,29 +120,49 @@ func (s *JIMMSuite) SetUpTest(c *gc.C) { mountHandler( "/.well-known", - jimmhttp.NewWellKnownHandler(s.JIMM.CredentialStore), + jimmhttp.NewWellKnownHandler(credentialStore), ) - macaroonDischarger := s.setupMacaroonDischarger(c) + macaroonDischarger := setupMacaroonDischarger(c, ControllerUUID, database, s.OFGAClient) localDischargePath := "/macaroons" mux.Handle(localDischargePath+"/*", discharger.GetDischargerMux(macaroonDischarger, localDischargePath)) s.Server = httptest.NewServer(mux) - s.JIMM.JWKService = jimmjwx.NewJWKSService(s.JIMM.CredentialStore) - err = s.JIMM.JWKService.StartJWKSRotator(ctx, time.NewTicker(time.Hour).C, time.Now().UTC().AddDate(0, 3, 0)) - c.Assert(err, gc.Equals, nil) - u, _ := url.Parse(s.Server.URL) - s.JIMM.JWTService = jimmjwx.NewJWTService(jimmjwx.JWTServiceParams{ + jwksService := jimmjwx.NewJWKSService(credentialStore) + err = jwksService.StartJWKSRotator(ctx, time.NewTicker(time.Hour).C, time.Now().UTC().AddDate(0, 3, 0)) + c.Assert(err, gc.Equals, nil) + + jwtService := jimmjwx.NewJWTService(jimmjwx.JWTServiceParams{ Host: u.Host, - Store: s.JIMM.CredentialStore, + Store: credentialStore, Expiry: time.Minute, }) - s.JIMM.Dialer = &jujuclient.Dialer{ - ControllerCredentialsStore: s.JIMM.CredentialStore, - JWTService: s.JIMM.JWTService, - } + + s.JIMM = NewJIMM(gct, &jimm.Parameters{ + UUID: ControllerUUID, + Database: database, + Dialer: &jujuclient.Dialer{ + ControllerCredentialsStore: credentialStore, + JWTService: jwtService, + }, + CredentialStore: credentialStore, + Pubsub: &pubsub.Hub{MaxConcurrency: 10}, + OpenFGAClient: s.OFGAClient, + OAuthAuthenticator: &authenticator, + + JWKService: jwksService, + JWTService: jwtService, + }) + + err = s.AdminUser.SetControllerAccess(ctx, s.JIMM.ResourceTag(), ofganames.AdministratorRelation) + c.Assert(err, gc.Equals, nil) + + // add jimmtest.DefaultControllerUUID as a controller to JIMM + err = s.OFGAClient.AddController(ctx, s.JIMM.ResourceTag(), names.NewControllerTag(DefaultControllerUUID)) + c.Assert(err, gc.Equals, nil) + } func (s *JIMMSuite) TearDownTest(c *gc.C) { @@ -162,6 +175,11 @@ func (s *JIMMSuite) TearDownTest(c *gc.C) { if err := s.JIMM.Database.Close(); err != nil { c.Logf("failed to close database connections at tear down: %s", err) } + + for _, cleanup := range s.databaseCleanup { + cleanup() + } + // Only delete the DB after closing connections to it. _, skipCleanup := os.LookupEnv("NO_DB_CLEANUP") if !skipCleanup { @@ -172,14 +190,14 @@ func (s *JIMMSuite) TearDownTest(c *gc.C) { } } -func (s *JIMMSuite) setupMacaroonDischarger(c *gc.C) *discharger.MacaroonDischarger { +func setupMacaroonDischarger(c *gc.C, uuid string, db *db.Database, ofgaClient *openfga.OFGAClient) *discharger.MacaroonDischarger { cfg := discharger.MacaroonDischargerConfig{ MacaroonExpiryDuration: 1 * time.Hour, - ControllerUUID: s.JIMM.UUID, + ControllerUUID: uuid, PrivateKey: "ly/dzsI9Nt/4JxUILQeAX79qZ4mygDiuYGqc2ZEiDEc=", PublicKey: "izcYsQy3TePp6bLjqOo3IRPFvkQd2IKtyODGqC6SdFk=", } - macaroonDischarger, err := discharger.NewMacaroonDischarger(cfg, &s.JIMM.Database, s.JIMM.OpenFGAClient) + macaroonDischarger, err := discharger.NewMacaroonDischarger(cfg, db, ofgaClient) c.Assert(err, gc.IsNil) return macaroonDischarger } @@ -277,14 +295,14 @@ func (s *JIMMSuite) AddModel(c *gc.C, owner names.UserTag, name string, cloud na func (s *JIMMSuite) AddGroup(c *gc.C, groupName string) dbmodel.GroupEntry { ctx := context.Background() - group, err := s.JIMM.GroupManager.AddGroup(ctx, s.AdminUser, groupName) + group, err := s.JIMM.GroupManager().AddGroup(ctx, s.AdminUser, groupName) c.Assert(err, gc.Equals, nil) return *group } func (s *JIMMSuite) AddRole(c *gc.C, roleName string) dbmodel.RoleEntry { ctx := context.Background() - role, err := s.JIMM.RoleManager.AddRole(ctx, s.AdminUser, roleName) + role, err := s.JIMM.RoleManager().AddRole(ctx, s.AdminUser, roleName) c.Assert(err, gc.Equals, nil) return *role }