From 63da5d1e9fdccf66d240e08dfb7c6f6ef2017c4a Mon Sep 17 00:00:00 2001 From: Kelvin Wang Date: Wed, 3 Oct 2018 10:51:14 -0400 Subject: [PATCH] (feat/testing) add onboarding and basic auth service --- bolt/basic_auth_service.go | 69 ++++++++++++ bolt/basic_auth_service_test.go | 41 +++++++ bolt/onboarding_test.go | 3 +- bolt/user.go | 61 ----------- bolt/user_resource_mapping.go | 2 + bolt/user_test.go | 184 -------------------------------- http/errors.go | 13 ++- http/onboarding.go | 18 ++-- http/onboarding_test.go | 47 ++++++++ http/user_test.go | 1 + inmem/basic_auth.go | 49 +++++++++ inmem/basic_auth_test.go | 31 ++++++ inmem/onboarding.go | 121 +++++++++++++++++++++ inmem/onboarding_test.go | 24 +++++ inmem/service.go | 2 + onboarding.go | 6 ++ testing/basic_auth.go | 171 +++++++++++++++++++++++++++++ testing/onboarding.go | 9 +- user.go | 1 + 19 files changed, 589 insertions(+), 264 deletions(-) create mode 100644 bolt/basic_auth_service.go create mode 100644 bolt/basic_auth_service_test.go create mode 100644 http/onboarding_test.go create mode 100644 inmem/basic_auth.go create mode 100644 inmem/basic_auth_test.go create mode 100644 inmem/onboarding.go create mode 100644 inmem/onboarding_test.go create mode 100644 testing/basic_auth.go diff --git a/bolt/basic_auth_service.go b/bolt/basic_auth_service.go new file mode 100644 index 0000000000..f3f5a82ebc --- /dev/null +++ b/bolt/basic_auth_service.go @@ -0,0 +1,69 @@ +package bolt + +import ( + "context" + + bolt "github.com/coreos/bbolt" + "golang.org/x/crypto/bcrypt" +) + +// SetPassword stores the password hash associated with a user. +func (c *Client) SetPassword(ctx context.Context, name string, password string) error { + return c.db.Update(func(tx *bolt.Tx) error { + return c.setPassword(ctx, tx, name, password) + }) +} + +// HashCost currently using the default cost of bcrypt +var HashCost = bcrypt.DefaultCost + +func (c *Client) setPassword(ctx context.Context, tx *bolt.Tx, name string, password string) error { + hash, err := bcrypt.GenerateFromPassword([]byte(password), HashCost) + if err != nil { + return err + } + + u, err := c.findUserByName(ctx, tx, name) + if err != nil { + return err + } + + encodedID, err := u.ID.Encode() + if err != nil { + return err + } + + return tx.Bucket(userpasswordBucket).Put(encodedID, hash) +} + +// ComparePassword compares a provided password with the stored password hash. +func (c *Client) ComparePassword(ctx context.Context, name string, password string) error { + return c.db.View(func(tx *bolt.Tx) error { + return c.comparePassword(ctx, tx, name, password) + }) +} +func (c *Client) comparePassword(ctx context.Context, tx *bolt.Tx, name string, password string) error { + u, err := c.findUserByName(ctx, tx, name) + if err != nil { + return err + } + + encodedID, err := u.ID.Encode() + if err != nil { + return err + } + + hash := tx.Bucket(userpasswordBucket).Get(encodedID) + + return bcrypt.CompareHashAndPassword(hash, []byte(password)) +} + +// CompareAndSetPassword replaces the old password with the new password if thee old password is correct. +func (c *Client) CompareAndSetPassword(ctx context.Context, name string, old string, new string) error { + return c.db.Update(func(tx *bolt.Tx) error { + if err := c.comparePassword(ctx, tx, name, old); err != nil { + return err + } + return c.setPassword(ctx, tx, name, new) + }) +} diff --git a/bolt/basic_auth_service_test.go b/bolt/basic_auth_service_test.go new file mode 100644 index 0000000000..7a0f67d671 --- /dev/null +++ b/bolt/basic_auth_service_test.go @@ -0,0 +1,41 @@ +package bolt_test + +import ( + "context" + "testing" + + "github.com/influxdata/platform" + platformtesting "github.com/influxdata/platform/testing" +) + +func initBasicAuthService(f platformtesting.UserFields, t *testing.T) (platform.BasicAuthService, func()) { + c, closeFn, err := NewTestClient() + if err != nil { + t.Fatalf("failed to create new bolt client: %v", err) + } + c.IDGenerator = f.IDGenerator + ctx := context.Background() + for _, u := range f.Users { + if err := c.PutUser(ctx, u); err != nil { + t.Fatalf("failed to populate users") + } + } + return c, func() { + defer closeFn() + for _, u := range f.Users { + if err := c.DeleteUser(ctx, u.ID); err != nil { + t.Logf("failed to remove users: %v", err) + } + } + } +} + +func TestBasicAuth(t *testing.T) { + t.Parallel() + platformtesting.BasicAuth(initBasicAuthService, t) +} + +func TestBasicAuth_CompareAndSet(t *testing.T) { + t.Parallel() + platformtesting.CompareAndSetPassword(initBasicAuthService, t) +} diff --git a/bolt/onboarding_test.go b/bolt/onboarding_test.go index 73b6002e5b..9bcc21a9b0 100644 --- a/bolt/onboarding_test.go +++ b/bolt/onboarding_test.go @@ -4,10 +4,11 @@ import ( "context" "testing" + "github.com/influxdata/platform" platformtesting "github.com/influxdata/platform/testing" ) -func initOnboardingService(f platformtesting.OnboardingFields, t *testing.T) (platformtesting.OnBoardingNBasicAuthService, func()) { +func initOnboardingService(f platformtesting.OnboardingFields, t *testing.T) (platform.OnboardingService, func()) { c, closeFn, err := NewTestClient() if err != nil { t.Fatalf("failed to create new bolt client: %v", err) diff --git a/bolt/user.go b/bolt/user.go index 236c93db2d..f7761f9b49 100644 --- a/bolt/user.go +++ b/bolt/user.go @@ -7,7 +7,6 @@ import ( "github.com/coreos/bbolt" "github.com/influxdata/platform" - "golang.org/x/crypto/bcrypt" ) var ( @@ -341,63 +340,3 @@ func (c *Client) deleteUsersAuthorizations(ctx context.Context, tx *bolt.Tx, id } return nil } - -// SetPassword stores the password hash associated with a user. -func (c *Client) SetPassword(ctx context.Context, name string, password string) error { - return c.db.Update(func(tx *bolt.Tx) error { - return c.setPassword(ctx, tx, name, password) - }) -} - -var HashCost = bcrypt.DefaultCost - -func (c *Client) setPassword(ctx context.Context, tx *bolt.Tx, name string, password string) error { - hash, err := bcrypt.GenerateFromPassword([]byte(password), HashCost) - if err != nil { - return err - } - - u, err := c.findUserByName(ctx, tx, name) - if err != nil { - return err - } - - encodedID, err := u.ID.Encode() - if err != nil { - return err - } - - return tx.Bucket(userpasswordBucket).Put(encodedID, hash) -} - -// ComparePassword compares a provided password with the stored password hash. -func (c *Client) ComparePassword(ctx context.Context, name string, password string) error { - return c.db.View(func(tx *bolt.Tx) error { - return c.comparePassword(ctx, tx, name, password) - }) -} -func (c *Client) comparePassword(ctx context.Context, tx *bolt.Tx, name string, password string) error { - u, err := c.findUserByName(ctx, tx, name) - if err != nil { - return err - } - - encodedID, err := u.ID.Encode() - if err != nil { - return err - } - - hash := tx.Bucket(userpasswordBucket).Get(encodedID) - - return bcrypt.CompareHashAndPassword(hash, []byte(password)) -} - -// CompareAndSetPassword replaces the old password with the new password if thee old password is correct. -func (c *Client) CompareAndSetPassword(ctx context.Context, name string, old string, new string) error { - return c.db.Update(func(tx *bolt.Tx) error { - if err := c.comparePassword(ctx, tx, name, old); err != nil { - return err - } - return c.setPassword(ctx, tx, name, new) - }) -} diff --git a/bolt/user_resource_mapping.go b/bolt/user_resource_mapping.go index 9aca07e873..e5a0c7c997 100644 --- a/bolt/user_resource_mapping.go +++ b/bolt/user_resource_mapping.go @@ -29,6 +29,7 @@ func filterMappingsFn(filter platform.UserResourceMappingFilter) func(m *platfor } } +// FindUserResourceMappings returns a list of UserResourceMappings that match filter and the total count of matching mappings. func (c *Client) FindUserResourceMappings(ctx context.Context, filter platform.UserResourceMappingFilter, opt ...platform.FindOptions) ([]*platform.UserResourceMapping, int, error) { ms := []*platform.UserResourceMapping{} err := c.db.View(func(tx *bolt.Tx) error { @@ -137,6 +138,7 @@ func (c *Client) uniqueUserResourceMapping(ctx context.Context, tx *bolt.Tx, m * return len(v) == 0 } +// DeleteUserResourceMapping deletes a user resource mapping. func (c *Client) DeleteUserResourceMapping(ctx context.Context, resourceID platform.ID, userID platform.ID) error { return c.db.Update(func(tx *bolt.Tx) error { return c.deleteUserResourceMapping(ctx, tx, platform.UserResourceMappingFilter{ diff --git a/bolt/user_test.go b/bolt/user_test.go index 5f3ade43da..0dd3947a4f 100644 --- a/bolt/user_test.go +++ b/bolt/user_test.go @@ -2,7 +2,6 @@ package bolt_test import ( "context" - "fmt" "testing" "github.com/influxdata/platform" @@ -54,186 +53,3 @@ func TestUserService_FindUser(t *testing.T) { func TestUserService_UpdateUser(t *testing.T) { platformtesting.UpdateUser(initUserService, t) } - -func TestBasicAuth(t *testing.T) { - type fields struct { - users []*platform.User - } - type args struct { - name string - user string - setPassword string - comparePassword string - } - type wants struct { - setErr error - compareErr error - } - tests := []struct { - fields fields - args args - wants wants - }{ - { - fields: fields{ - users: []*platform.User{ - { - Name: "user1", - ID: platformtesting.MustIDBase16("aaaaaaaaaaaaaaaa"), - }, - }, - }, - args: args{ - name: "happy path", - user: "user1", - setPassword: "hello", - comparePassword: "hello", - }, - wants: wants{}, - }, - { - fields: fields{ - users: []*platform.User{ - { - Name: "user1", - ID: platformtesting.MustIDBase16("aaaaaaaaaaaaaaaa"), - }, - }, - }, - args: args{ - name: "happy path dont match", - user: "user1", - setPassword: "hello", - comparePassword: "world", - }, - wants: wants{ - compareErr: fmt.Errorf("crypto/bcrypt: hashedPassword is not the hash of the given password"), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.args.name, func(t *testing.T) { - c, closeFn, err := NewTestClient() - if err != nil { - t.Fatalf("failed to create new bolt client: %v", err) - } - defer closeFn() - ctx := context.Background() - - for _, user := range tt.fields.users { - if err := c.PutUser(ctx, user); err != nil { - t.Fatal(err) - return - } - } - - err = c.SetPassword(ctx, tt.args.user, tt.args.setPassword) - - if (err != nil && tt.wants.setErr == nil) || (err == nil && tt.wants.setErr != nil) { - t.Fatalf("expected SetPassword error %v got %v", tt.wants.setErr, err) - return - } - - if err != nil { - if want, got := tt.wants.setErr.Error(), err.Error(); want != got { - t.Fatalf("expected SetPassword error %v got %v", want, got) - } - return - } - - err = c.ComparePassword(ctx, tt.args.user, tt.args.comparePassword) - - if (err != nil && tt.wants.compareErr == nil) || (err == nil && tt.wants.compareErr != nil) { - t.Fatalf("expected ComparePassword error %v got %v", tt.wants.compareErr, err) - return - } - - if err != nil { - if want, got := tt.wants.compareErr.Error(), err.Error(); want != got { - t.Fatalf("expected ComparePassword error %v got %v", tt.wants.compareErr, err) - } - return - } - - }) - } - -} - -func TestBasicAuth_CompareAndSet(t *testing.T) { - type fields struct { - users []*platform.User - } - type args struct { - name string - user string - old string - new string - } - type wants struct { - err error - } - tests := []struct { - fields fields - args args - wants wants - }{ - { - fields: fields{ - users: []*platform.User{ - { - Name: "user1", - ID: platformtesting.MustIDBase16("aaaaaaaaaaaaaaaa"), - }, - }, - }, - args: args{ - name: "happy path", - user: "user1", - old: "hello", - new: "hello", - }, - wants: wants{}, - }, - } - - for _, tt := range tests { - t.Run(tt.args.name, func(t *testing.T) { - c, closeFn, err := NewTestClient() - if err != nil { - t.Fatalf("failed to create new bolt client: %v", err) - } - defer closeFn() - ctx := context.Background() - - for _, user := range tt.fields.users { - if err := c.PutUser(ctx, user); err != nil { - t.Fatal(err) - return - } - } - - if err := c.SetPassword(ctx, tt.args.user, tt.args.old); err != nil { - t.Fatalf("unexpected error %v", err) - return - } - - err = c.CompareAndSetPassword(ctx, tt.args.user, tt.args.old, tt.args.new) - - if (err != nil && tt.wants.err == nil) || (err == nil && tt.wants.err != nil) { - t.Fatalf("expected CompareAndSetPassword error %v got %v", tt.wants.err, err) - return - } - - if err != nil { - if want, got := tt.wants.err.Error(), err.Error(); want != got { - t.Fatalf("expected CompareAndSetPassword error %v got %v", tt.wants.err, err) - } - return - } - - }) - } - -} diff --git a/http/errors.go b/http/errors.go index daad8ea7ff..4acd5a8559 100644 --- a/http/errors.go +++ b/http/errors.go @@ -52,7 +52,8 @@ func CheckErrorStatus(code int, res *http.Response) error { // be determined in that way, it will create a generic error message. // // If there is no error, then this returns nil. -func CheckError(resp *http.Response) error { +// Add an optional isPlatformError, to do decode with platform.Error +func CheckError(resp *http.Response, isPlatformError ...bool) (err error) { switch resp.StatusCode / 100 { case 4, 5: // We will attempt to parse this error outside of this block. @@ -62,7 +63,15 @@ func CheckError(resp *http.Response) error { // TODO(jsternberg): Figure out what to do here? return kerrors.InternalErrorf("unexpected status code: %d %s", resp.StatusCode, resp.Status) } - + if len(isPlatformError) > 0 && isPlatformError[0] { + pe := new(platform.Error) + parseErr := json.NewDecoder(resp.Body).Decode(pe) + if parseErr != nil { + return parseErr + } + err = pe + return err + } // Attempt to read the X-Influx-Error header with the message. if errMsg := resp.Header.Get(ErrorHeader); errMsg != "" { // Parse the reference number as an integer. If we cannot parse it, diff --git a/http/onboarding.go b/http/onboarding.go index 2fb5071a14..a875e9d6c7 100644 --- a/http/onboarding.go +++ b/http/onboarding.go @@ -148,8 +148,9 @@ func (s *SetupService) Generate(ctx context.Context, or *platform.OnboardingRequ if err != nil { return nil, err } + defer resp.Body.Close() // TODO(jsternberg): Should this check for a 201 explicitly? - if err := CheckError(resp); err != nil { + if err := CheckError(resp, true); err != nil { return nil, err } @@ -158,13 +159,14 @@ func (s *SetupService) Generate(ctx context.Context, or *platform.OnboardingRequ return nil, err } + bkt, err := oResp.Bucket.toPlatform() + if err != nil { + return nil, err + } return &platform.OnboardingResults{ - User: &oResp.User.User, - Auth: &oResp.Auth.Authorization, - Org: &oResp.Organization.Organization, - Bucket: &platform.Bucket{ - ID: oResp.Bucket.ID, - Name: oResp.Bucket.Name, - }, + User: &oResp.User.User, + Auth: &oResp.Auth.Authorization, + Org: &oResp.Organization.Organization, + Bucket: bkt, }, nil } diff --git a/http/onboarding_test.go b/http/onboarding_test.go new file mode 100644 index 0000000000..8013c6484a --- /dev/null +++ b/http/onboarding_test.go @@ -0,0 +1,47 @@ +package http + +import ( + "context" + "net/http/httptest" + "testing" + + "github.com/influxdata/platform" + "github.com/influxdata/platform/inmem" + platformtesting "github.com/influxdata/platform/testing" +) + +func initOnboardingService(f platformtesting.OnboardingFields, t *testing.T) (platform.OnboardingService, func()) { + t.Helper() + svc := inmem.NewService() + svc.IDGenerator = f.IDGenerator + svc.TokenGenerator = f.TokenGenerator + + ctx := context.Background() + if err := svc.PutOnboardingStatus(ctx, !f.IsOnboarding); err != nil { + t.Fatalf("failed to set new onboarding finished: %v", err) + } + + handler := NewSetupHandler() + handler.OnboardingService = svc + server := httptest.NewServer(handler) + client := struct { + *SetupService + *Service + platform.BasicAuthService + }{ + SetupService: &SetupService{ + Addr: server.URL, + }, + Service: &Service{ + Addr: server.URL, + }, + BasicAuthService: svc, + } + + done := server.Close + + return client, done +} +func TestOnboardingService(t *testing.T) { + platformtesting.Generate(initOnboardingService, t) +} diff --git a/http/user_test.go b/http/user_test.go index 6ad83467d9..e374845b4a 100644 --- a/http/user_test.go +++ b/http/user_test.go @@ -28,6 +28,7 @@ func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserS client := UserService{ Addr: server.URL, } + done := server.Close return &client, done diff --git a/inmem/basic_auth.go b/inmem/basic_auth.go new file mode 100644 index 0000000000..1ccff30f2d --- /dev/null +++ b/inmem/basic_auth.go @@ -0,0 +1,49 @@ +package inmem + +import ( + "context" + + "github.com/influxdata/platform" + "golang.org/x/crypto/bcrypt" +) + +// HashCost is currently using bcrypt defaultCost +const HashCost = bcrypt.DefaultCost + +// SetPassword stores the password hash associated with a user. +func (s *Service) SetPassword(ctx context.Context, name string, password string) error { + u, err := s.FindUser(ctx, platform.UserFilter{Name: &name}) + if err != nil { + return err + } + hash, err := bcrypt.GenerateFromPassword([]byte(password), HashCost) + if err != nil { + return err + } + + s.basicAuthKV.Store(u.ID.String(), hash) + + return nil +} + +// ComparePassword compares a provided password with the stored password hash. +func (s *Service) ComparePassword(ctx context.Context, name string, password string) error { + u, err := s.FindUser(ctx, platform.UserFilter{Name: &name}) + if err != nil { + return err + } + hash, ok := s.basicAuthKV.Load(u.ID.String()) + if !ok { + hash = []byte{} + } + + return bcrypt.CompareHashAndPassword(hash.([]byte), []byte(password)) +} + +// CompareAndSetPassword replaces the old password with the new password if thee old password is correct. +func (s *Service) CompareAndSetPassword(ctx context.Context, name string, old string, new string) error { + if err := s.ComparePassword(ctx, name, old); err != nil { + return err + } + return s.SetPassword(ctx, name, new) +} diff --git a/inmem/basic_auth_test.go b/inmem/basic_auth_test.go new file mode 100644 index 0000000000..aef2ed96f8 --- /dev/null +++ b/inmem/basic_auth_test.go @@ -0,0 +1,31 @@ +package inmem + +import ( + "context" + "testing" + + "github.com/influxdata/platform" + platformtesting "github.com/influxdata/platform/testing" +) + +func initBasicAuthService(f platformtesting.UserFields, t *testing.T) (platform.BasicAuthService, func()) { + s := NewService() + s.IDGenerator = f.IDGenerator + ctx := context.Background() + for _, u := range f.Users { + if err := s.PutUser(ctx, u); err != nil { + t.Fatalf("failed to populate users") + } + } + return s, func() {} +} + +func TestBasicAuth(t *testing.T) { + t.Parallel() + platformtesting.BasicAuth(initBasicAuthService, t) +} + +func TestBasicAuth_CompareAndSet(t *testing.T) { + t.Parallel() + platformtesting.CompareAndSetPassword(initBasicAuthService, t) +} diff --git a/inmem/onboarding.go b/inmem/onboarding.go new file mode 100644 index 0000000000..a6f12c0136 --- /dev/null +++ b/inmem/onboarding.go @@ -0,0 +1,121 @@ +package inmem + +import ( + "context" + "time" + + "github.com/influxdata/platform" +) + +const onboardingKey = "onboarding_key" + +var _ platform.OnboardingService = (*Service)(nil) + +// IsOnboarding checks onboardingBucket +// to see if the onboarding key is true. +func (s *Service) IsOnboarding(ctx context.Context) (isOnboarding bool, err error) { + result, ok := s.onboardingKV.Load(onboardingKey) + isOnboarding = !ok || !result.(bool) + return isOnboarding, nil +} + +// PutOnboardingStatus will put the isOnboarding to storage +func (s *Service) PutOnboardingStatus(ctx context.Context, v bool) error { + s.onboardingKV.Store(onboardingKey, v) + return nil +} + +// Generate OnboardingResults from onboarding request, +// update storage so this request will be disabled for the second run. +func (s *Service) Generate(ctx context.Context, req *platform.OnboardingRequest) (*platform.OnboardingResults, error) { + isOnboarding, err := s.IsOnboarding(ctx) + if err != nil { + return nil, err + } + if !isOnboarding { + return nil, &platform.Error{ + Code: platform.EConflict, + Msg: "onboarding has already been completed", + } + } + + if req.Password == "" { + return nil, &platform.Error{ + Code: platform.EEmptyValue, + Msg: "password is empty", + } + } + + if req.User == "" { + return nil, &platform.Error{ + Code: platform.EEmptyValue, + Msg: "username is empty", + } + } + + if req.Org == "" { + return nil, &platform.Error{ + Code: platform.EEmptyValue, + Msg: "org name is empty", + } + } + + if req.Bucket == "" { + return nil, &platform.Error{ + Code: platform.EEmptyValue, + Msg: "bucket name is empty", + } + } + + u := &platform.User{Name: req.User} + if err := s.CreateUser(ctx, u); err != nil { + return nil, err + } + + if err = s.SetPassword(ctx, u.Name, req.Password); err != nil { + return nil, err + } + + o := &platform.Organization{ + Name: req.Org, + } + if err = s.CreateOrganization(ctx, o); err != nil { + return nil, err + } + bucket := &platform.Bucket{ + Name: req.Bucket, + Organization: o.Name, + OrganizationID: o.ID, + RetentionPeriod: time.Duration(req.RetentionPeriod) * time.Hour, + } + if err = s.CreateBucket(ctx, bucket); err != nil { + return nil, err + } + auth := &platform.Authorization{ + User: u.Name, + UserID: u.ID, + Permissions: []platform.Permission{ + platform.CreateUserPermission, + platform.DeleteUserPermission, + platform.Permission{ + Resource: platform.OrganizationResource, + Action: platform.WriteAction, + }, + platform.WriteBucketPermission(bucket.ID), + }, + } + if err = s.CreateAuthorization(ctx, auth); err != nil { + return nil, err + } + + if err = s.PutOnboardingStatus(ctx, true); err != nil { + return nil, err + } + + return &platform.OnboardingResults{ + User: u, + Org: o, + Bucket: bucket, + Auth: auth, + }, nil +} diff --git a/inmem/onboarding_test.go b/inmem/onboarding_test.go new file mode 100644 index 0000000000..668dde5c52 --- /dev/null +++ b/inmem/onboarding_test.go @@ -0,0 +1,24 @@ +package inmem + +import ( + "context" + "testing" + + "github.com/influxdata/platform" + platformtesting "github.com/influxdata/platform/testing" +) + +func initOnboardingService(f platformtesting.OnboardingFields, t *testing.T) (platform.OnboardingService, func()) { + s := NewService() + s.IDGenerator = f.IDGenerator + s.TokenGenerator = f.TokenGenerator + ctx := context.TODO() + if err := s.PutOnboardingStatus(ctx, !f.IsOnboarding); err != nil { + t.Fatalf("failed to set new onboarding finished: %v", err) + } + return s, func() {} +} + +func TestGenerate(t *testing.T) { + platformtesting.Generate(initOnboardingService, t) +} diff --git a/inmem/service.go b/inmem/service.go index e787dbe94b..57eca516c3 100644 --- a/inmem/service.go +++ b/inmem/service.go @@ -22,6 +22,8 @@ type Service struct { userResourceMappingKV sync.Map scraperTargetKV sync.Map telegrafConfigKV sync.Map + onboardingKV sync.Map + basicAuthKV sync.Map TokenGenerator platform.TokenGenerator IDGenerator platform.IDGenerator diff --git a/onboarding.go b/onboarding.go index a29cbfd780..b2440c10b4 100644 --- a/onboarding.go +++ b/onboarding.go @@ -22,6 +22,12 @@ type OnboardingRequest struct { // OnboardingService represents a service for the first run. type OnboardingService interface { + BasicAuthService + BucketService + OrganizationService + UserService + AuthorizationService + // IsOnboarding determine if onboarding request is allowed. IsOnboarding(ctx context.Context) (bool, error) // Generate OnboardingResults. diff --git a/testing/basic_auth.go b/testing/basic_auth.go new file mode 100644 index 0000000000..af0f0f75e6 --- /dev/null +++ b/testing/basic_auth.go @@ -0,0 +1,171 @@ +package testing + +import ( + "context" + "fmt" + "testing" + + "github.com/influxdata/platform" +) + +// BasicAuth test all the services for basic auth +func BasicAuth( + init func(UserFields, *testing.T) (platform.BasicAuthService, func()), + t *testing.T) { + type args struct { + name string + user string + setPassword string + comparePassword string + } + type wants struct { + setErr error + compareErr error + } + tests := []struct { + fields UserFields + args args + wants wants + }{ + { + fields: UserFields{ + Users: []*platform.User{ + { + Name: "user1", + ID: MustIDBase16(oneID), + }, + }, + }, + args: args{ + name: "happy path", + user: "user1", + setPassword: "hello", + comparePassword: "hello", + }, + wants: wants{}, + }, + { + fields: UserFields{ + Users: []*platform.User{ + { + Name: "user1", + ID: MustIDBase16(oneID), + }, + }, + }, + args: args{ + name: "happy path dont match", + user: "user1", + setPassword: "hello", + comparePassword: "world", + }, + wants: wants{ + compareErr: fmt.Errorf("crypto/bcrypt: hashedPassword is not the hash of the given password"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.args.name, func(t *testing.T) { + s, done := init(tt.fields, t) + defer done() + ctx := context.Background() + + err := s.SetPassword(ctx, tt.args.user, tt.args.setPassword) + + if (err != nil && tt.wants.setErr == nil) || (err == nil && tt.wants.setErr != nil) { + t.Fatalf("expected SetPassword error %v got %v", tt.wants.setErr, err) + return + } + + if err != nil { + if want, got := tt.wants.setErr.Error(), err.Error(); want != got { + t.Fatalf("expected SetPassword error %v got %v", want, got) + } + return + } + + err = s.ComparePassword(ctx, tt.args.user, tt.args.comparePassword) + + if (err != nil && tt.wants.compareErr == nil) || (err == nil && tt.wants.compareErr != nil) { + t.Fatalf("expected ComparePassword error %v got %v", tt.wants.compareErr, err) + return + } + + if err != nil { + if want, got := tt.wants.compareErr.Error(), err.Error(); want != got { + t.Fatalf("expected ComparePassword error %v got %v", tt.wants.compareErr, err) + } + return + } + + }) + } + +} + +// CompareAndSetPassword test +func CompareAndSetPassword( + init func(UserFields, *testing.T) (platform.BasicAuthService, func()), + t *testing.T) { + type args struct { + name string + user string + old string + new string + } + type wants struct { + err error + } + tests := []struct { + fields UserFields + args args + wants wants + }{ + { + fields: UserFields{ + Users: []*platform.User{ + { + Name: "user1", + ID: MustIDBase16(oneID), + }, + }, + }, + args: args{ + name: "happy path", + user: "user1", + old: "hello", + new: "hello", + }, + wants: wants{}, + }, + } + + for _, tt := range tests { + t.Run(tt.args.name, func(t *testing.T) { + s, done := init(tt.fields, t) + defer done() + ctx := context.Background() + if err := s.SetPassword(ctx, tt.args.user, tt.args.old); err != nil { + t.Fatalf("unexpected error %v", err) + return + } + + err := s.CompareAndSetPassword(ctx, tt.args.user, tt.args.old, tt.args.new) + + if (err != nil && tt.wants.err == nil) || (err == nil && tt.wants.err != nil) { + t.Fatalf("expected CompareAndSetPassword error %v got %v", tt.wants.err, err) + return + } + + if err != nil { + if want, got := tt.wants.err.Error(), err.Error(); want != got { + t.Fatalf("expected CompareAndSetPassword error %v got %v", tt.wants.err, err) + } + return + } + + }) + } + +} diff --git a/testing/onboarding.go b/testing/onboarding.go index 99fd618a75..b6d38a147d 100644 --- a/testing/onboarding.go +++ b/testing/onboarding.go @@ -18,16 +18,9 @@ type OnboardingFields struct { IsOnboarding bool } -// OnBoardingNBasicAuthService includes onboarding service -// and basic auth service. -type OnBoardingNBasicAuthService interface { - platform.OnboardingService - platform.BasicAuthService -} - // Generate testing func Generate( - init func(OnboardingFields, *testing.T) (OnBoardingNBasicAuthService, func()), + init func(OnboardingFields, *testing.T) (platform.OnboardingService, func()), t *testing.T, ) { type args struct { diff --git a/user.go b/user.go index b6a8b591b8..a9b1282966 100644 --- a/user.go +++ b/user.go @@ -10,6 +10,7 @@ type User struct { // UserService represents a service for managing user data. type UserService interface { + // Returns a single user by ID. FindUserByID(ctx context.Context, id ID) (*User, error)