diff --git a/pkg/models/org_user.go b/pkg/models/org_user.go index 5fc3c80079c..41bce5c2231 100644 --- a/pkg/models/org_user.go +++ b/pkg/models/org_user.go @@ -107,22 +107,20 @@ type UpdateOrgUserCommand struct { // QUERIES type GetOrgUsersQuery struct { - UserID int64 - OrgId int64 - Query string - Limit int - IsServiceAccount bool + UserID int64 + OrgId int64 + Query string + Limit int User *SignedInUser Result []*OrgUserDTO } type SearchOrgUsersQuery struct { - OrgID int64 - Query string - Page int - Limit int - IsServiceAccount bool + OrgID int64 + Query string + Page int + Limit int User *SignedInUser Result SearchOrgUsersQueryResult diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index 0df95ce25a3..67002a1cb54 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -55,7 +55,6 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints( auth := acmiddleware.Middleware(api.accesscontrol) api.RouterRegister.Group("/api/serviceaccounts", func(serviceAccountsRoute routing.RouteRegister) { - serviceAccountsRoute.Get("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeAll)), routing.Wrap(api.ListServiceAccounts)) serviceAccountsRoute.Get("/search", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead)), routing.Wrap(api.SearchOrgServiceAccountsWithPaging)) serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.CreateServiceAccount)) @@ -128,27 +127,6 @@ func (api *ServiceAccountsAPI) ConvertToServiceAccount(ctx *models.ReqContext) r } } -func (api *ServiceAccountsAPI) ListServiceAccounts(c *models.ReqContext) response.Response { - serviceAccounts, err := api.store.ListServiceAccounts(c.Req.Context(), c.OrgId, -1) - if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to list service accounts", err) - } - - saIDs := map[string]bool{} - for i := range serviceAccounts { - serviceAccounts[i].AvatarUrl = dtos.GetGravatarUrlWithDefault("", serviceAccounts[i].Name) - saIDs[strconv.FormatInt(serviceAccounts[i].Id, 10)] = true - } - - metadata := api.getAccessControlMetadata(c, saIDs) - if len(metadata) > 0 { - for i := range serviceAccounts { - serviceAccounts[i].AccessControl = metadata[strconv.FormatInt(serviceAccounts[i].Id, 10)] - } - } - return response.JSON(http.StatusOK, serviceAccounts) -} - func (api *ServiceAccountsAPI) getAccessControlMetadata(c *models.ReqContext, saIDs map[string]bool) map[string]accesscontrol.Metadata { if api.accesscontrol.IsDisabled() || !c.QueryBool("accesscontrol") { return map[string]accesscontrol.Metadata{} @@ -229,45 +207,26 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *models.ReqC if page < 1 { page = 1 } - query := &models.SearchOrgUsersQuery{ - OrgID: c.OrgId, - Query: c.Query("query"), - Page: page, - Limit: perPage, - User: c.SignedInUser, - IsServiceAccount: true, - } - serviceAccounts, err := api.store.SearchOrgServiceAccounts(ctx, query) + serviceAccountSearch, err := api.store.SearchOrgServiceAccounts(ctx, c.OrgId, c.Query("query"), page, perPage, c.SignedInUser) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to get service accounts for current organization", err) } saIDs := map[string]bool{} - for i := range serviceAccounts { - serviceAccounts[i].AvatarUrl = dtos.GetGravatarUrlWithDefault("", serviceAccounts[i].Name) + for i := range serviceAccountSearch.ServiceAccounts { + sa := serviceAccountSearch.ServiceAccounts[i] + sa.AvatarUrl = dtos.GetGravatarUrlWithDefault("", sa.Name) - saIDString := strconv.FormatInt(serviceAccounts[i].Id, 10) + saIDString := strconv.FormatInt(sa.Id, 10) saIDs[saIDString] = true metadata := api.getAccessControlMetadata(c, map[string]bool{saIDString: true}) - serviceAccounts[i].AccessControl = metadata[strconv.FormatInt(serviceAccounts[i].Id, 10)] - tokens, err := api.store.ListTokens(ctx, serviceAccounts[i].OrgId, serviceAccounts[i].Id) + sa.AccessControl = metadata[strconv.FormatInt(sa.Id, 10)] + tokens, err := api.store.ListTokens(ctx, sa.OrgId, sa.Id) if err != nil { - api.log.Warn("Failed to list tokens for service account", "serviceAccount", serviceAccounts[i].Id) + api.log.Warn("Failed to list tokens for service account", "serviceAccount", sa.Id) } - serviceAccounts[i].Tokens = int64(len(tokens)) + sa.Tokens = int64(len(tokens)) } - type searchOrgServiceAccountsQueryResult struct { - TotalCount int64 `json:"totalCount"` - ServiceAccounts []*serviceaccounts.ServiceAccountDTO `json:"serviceAccounts"` - Page int `json:"page"` - PerPage int `json:"perPage"` - } - result := searchOrgServiceAccountsQueryResult{ - TotalCount: query.Result.TotalCount, - ServiceAccounts: serviceAccounts, - Page: query.Result.Page, - PerPage: query.Result.PerPage, - } - return response.JSON(http.StatusOK, result) + return response.JSON(http.StatusOK, serviceAccountSearch) } diff --git a/pkg/services/serviceaccounts/api/api_test.go b/pkg/services/serviceaccounts/api/api_test.go index ab9156d4ffe..a5ed51dcde1 100644 --- a/pkg/services/serviceaccounts/api/api_test.go +++ b/pkg/services/serviceaccounts/api/api_test.go @@ -120,7 +120,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { serviceAccountRequestScenario(t, http.MethodPost, serviceAccountPath, testUser, func(httpmethod string, endpoint string, user *tests.TestUser) { - server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, database.NewServiceAccountsStore(store)) + server, _ := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, database.NewServiceAccountsStore(store)) marshalled, err := json.Marshal(tc.body) require.NoError(t, err) @@ -179,7 +179,7 @@ func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) { } serviceAccountRequestScenario(t, http.MethodDelete, serviceAccountIDPath, &testcase.user, func(httpmethod string, endpoint string, user *tests.TestUser) { createduser := tests.SetupUserServiceAccount(t, store, testcase.user) - server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), testcase.acmock, store, database.NewServiceAccountsStore(store)) + server, _ := setupTestServer(t, &svcmock, routing.NewRouteRegister(), testcase.acmock, store, database.NewServiceAccountsStore(store)) actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, fmt.Sprint(createduser.Id))).Code require.Equal(t, testcase.expectedCode, actual) }) @@ -203,7 +203,7 @@ func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) { } serviceAccountRequestScenario(t, http.MethodDelete, serviceAccountIDPath, &testcase.user, func(httpmethod string, endpoint string, user *tests.TestUser) { createduser := tests.SetupUserServiceAccount(t, store, testcase.user) - server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), testcase.acmock, store, database.NewServiceAccountsStore(store)) + server, _ := setupTestServer(t, &svcmock, routing.NewRouteRegister(), testcase.acmock, store, database.NewServiceAccountsStore(store)) actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, createduser.Id)).Code require.Equal(t, testcase.expectedCode, actual) }) @@ -218,7 +218,7 @@ func serviceAccountRequestScenario(t *testing.T, httpMethod string, endpoint str func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock, routerRegister routing.RouteRegister, acmock *accesscontrolmock.Mock, - sqlStore *sqlstore.SQLStore, saStore serviceaccounts.Store) *web.Mux { + sqlStore *sqlstore.SQLStore, saStore serviceaccounts.Store) (*web.Mux, *ServiceAccountsAPI) { a := NewServiceAccountsAPI(setting.NewCfg(), svc, acmock, routerRegister, saStore) a.RegisterAPIEndpoints(featuremgmt.WithFeatures(featuremgmt.FlagServiceAccounts)) @@ -240,7 +240,7 @@ func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock, c.Map(ctx) }) a.RouterRegister.Register(m.Router) - return m + return m, a } func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) { @@ -309,7 +309,7 @@ func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) { createdUser := tests.SetupUserServiceAccount(t, store, *tc.user) scopeID = int(createdUser.Id) } - server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, database.NewServiceAccountsStore(store)) + server, _ := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, database.NewServiceAccountsStore(store)) actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, scopeID)) @@ -413,43 +413,40 @@ func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - serviceAccountRequestScenario(t, http.MethodPatch, serviceAccountIDPath, tc.user, func(httpmethod string, endpoint string, user *tests.TestUser) { - scopeID := tc.Id - if tc.user != nil { - createdUser := tests.SetupUserServiceAccount(t, store, *tc.user) - scopeID = int(createdUser.Id) - } - server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, database.NewServiceAccountsStore(store)) + server, saAPI := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, database.NewServiceAccountsStore(store)) + scopeID := tc.Id + if tc.user != nil { + createdUser := tests.SetupUserServiceAccount(t, store, *tc.user) + scopeID = int(createdUser.Id) + } - var rawBody io.Reader = http.NoBody - if tc.body != nil { - body, err := json.Marshal(tc.body) - require.NoError(t, err) - rawBody = bytes.NewReader(body) - } + var rawBody io.Reader = http.NoBody + if tc.body != nil { + body, err := json.Marshal(tc.body) + require.NoError(t, err) + rawBody = bytes.NewReader(body) + } - actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, scopeID), rawBody) + actual := requestResponse(server, http.MethodPatch, fmt.Sprintf(serviceAccountIDPath, scopeID), rawBody) - actualCode := actual.Code - require.Equal(t, tc.expectedCode, actualCode) + actualCode := actual.Code + require.Equal(t, tc.expectedCode, actualCode) - if actualCode == http.StatusOK { - actualBody := map[string]interface{}{} - err := json.Unmarshal(actual.Body.Bytes(), &actualBody) - require.NoError(t, err) - assert.Equal(t, scopeID, int(actualBody["id"].(float64))) - assert.Equal(t, string(*tc.body.Role), actualBody["role"].(string)) - assert.Equal(t, *tc.body.Name, actualBody["name"].(string)) - assert.Equal(t, tc.user.Login, actualBody["login"].(string)) + if actualCode == http.StatusOK { + actualBody := map[string]interface{}{} + err := json.Unmarshal(actual.Body.Bytes(), &actualBody) + require.NoError(t, err) + assert.Equal(t, scopeID, int(actualBody["id"].(float64))) + assert.Equal(t, string(*tc.body.Role), actualBody["role"].(string)) + assert.Equal(t, *tc.body.Name, actualBody["name"].(string)) + assert.Equal(t, tc.user.Login, actualBody["login"].(string)) - // Ensure the user was updated in DB - query := models.GetOrgUsersQuery{UserID: int64(scopeID), OrgId: 1, IsServiceAccount: true} - err = store.GetOrgUsers(context.Background(), &query) - require.NoError(t, err) - require.Equal(t, *tc.body.Name, query.Result[0].Name) - require.Equal(t, string(*tc.body.Role), query.Result[0].Role) - } - }) + // Ensure the user was updated in DB + sa, err := saAPI.store.RetrieveServiceAccount(context.Background(), 1, int64(scopeID)) + require.NoError(t, err) + require.Equal(t, *tc.body.Name, sa.Name) + require.Equal(t, string(*tc.body.Role), sa.Role) + } }) } } diff --git a/pkg/services/serviceaccounts/api/token_test.go b/pkg/services/serviceaccounts/api/token_test.go index 6bac8f667af..38eb95de0bb 100644 --- a/pkg/services/serviceaccounts/api/token_test.go +++ b/pkg/services/serviceaccounts/api/token_test.go @@ -130,7 +130,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) { bodyString = string(b) } - server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, database.NewServiceAccountsStore(store)) + server, _ := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, database.NewServiceAccountsStore(store)) actual := requestResponse(server, http.MethodPost, endpoint, strings.NewReader(bodyString)) actualCode := actual.Code @@ -221,7 +221,7 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) { endpoint := fmt.Sprintf(serviceaccountIDTokensDetailPath, sa.Id, token.Id) bodyString := "" - server := setupTestServer(t, svcMock, routing.NewRouteRegister(), tc.acmock, store, saStore) + server, _ := setupTestServer(t, svcMock, routing.NewRouteRegister(), tc.acmock, store, saStore) actual := requestResponse(server, http.MethodDelete, endpoint, strings.NewReader(bodyString)) actualCode := actual.Code @@ -343,7 +343,7 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { endpoint := fmt.Sprintf(serviceAccountIDPath+"/tokens", sa.Id) - server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, &saStoreMockTokens{saAPIKeys: tc.tokens}) + server, _ := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, &saStoreMockTokens{saAPIKeys: tc.tokens}) actual := requestResponse(server, http.MethodGet, endpoint, http.NoBody) actualCode := actual.Code diff --git a/pkg/services/serviceaccounts/database/database.go b/pkg/services/serviceaccounts/database/database.go index c451eae27cc..a1e8bc65622 100644 --- a/pkg/services/serviceaccounts/database/database.go +++ b/pkg/services/serviceaccounts/database/database.go @@ -54,33 +54,37 @@ func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, org Tokens: 0, }, nil } - -func (s *ServiceAccountsStoreImpl) DeleteServiceAccount(ctx context.Context, orgID, serviceaccountID int64) error { - return s.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - return deleteServiceAccountInTransaction(sess, orgID, serviceaccountID) - }) +func ServiceAccountDeletions() []string { + deletes := []string{ + "DELETE FROM api_key WHERE service_account_id = ?", + } + deletes = append(deletes, sqlstore.UserDeletions()...) + return deletes } -func deleteServiceAccountInTransaction(sess *sqlstore.DBSession, orgID, serviceAccountID int64) error { - user := models.User{} - has, err := sess.Where(`org_id = ? and id = ? and is_service_account = true`, orgID, serviceAccountID).Get(&user) - if err != nil { - return err - } - if !has { - return serviceaccounts.ErrServiceAccountNotFound - } - for _, sql := range sqlstore.ServiceAccountDeletions() { - _, err := sess.Exec(sql, user.Id) +func (s *ServiceAccountsStoreImpl) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error { + return s.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + user := models.User{} + has, err := sess.Where(`org_id = ? and id = ? and is_service_account = ?`, + orgID, serviceAccountID, s.sqlStore.Dialect.BooleanStr(true)).Get(&user) if err != nil { return err } - } - return nil + if !has { + return serviceaccounts.ErrServiceAccountNotFound + } + for _, sql := range ServiceAccountDeletions() { + _, err := sess.Exec(sql, user.Id) + if err != nil { + return err + } + } + return nil + }) } func (s *ServiceAccountsStoreImpl) UpgradeServiceAccounts(ctx context.Context) error { - basicKeys := s.sqlStore.GetNonServiceAccountAPIKeys(ctx) + basicKeys := s.sqlStore.GetAllOrgsAPIKeys(ctx) if len(basicKeys) > 0 { s.log.Info("Launching background thread to upgrade API keys to service accounts", "numberKeys", len(basicKeys)) go func() { @@ -96,7 +100,7 @@ func (s *ServiceAccountsStoreImpl) UpgradeServiceAccounts(ctx context.Context) e } func (s *ServiceAccountsStoreImpl) ConvertToServiceAccounts(ctx context.Context, keys []int64) error { - basicKeys := s.sqlStore.GetNonServiceAccountAPIKeys(ctx) + basicKeys := s.sqlStore.GetAllOrgsAPIKeys(ctx) if len(basicKeys) == 0 { return nil } @@ -117,16 +121,29 @@ func (s *ServiceAccountsStoreImpl) ConvertToServiceAccounts(ctx context.Context, } func (s *ServiceAccountsStoreImpl) CreateServiceAccountFromApikey(ctx context.Context, key *models.ApiKey) error { - sa, err := s.sqlStore.CreateServiceAccountForApikey(ctx, key.OrgId, key.Name, key.Role) - if err != nil { - return fmt.Errorf("failed to create service account for API key with error : %w", err) + prefix := "sa-autogen-" + cmd := models.CreateUserCommand{ + Login: fmt.Sprintf("%v-%v-%v", prefix, key.OrgId, key.Name), + Name: prefix + key.Name, + OrgId: key.OrgId, + DefaultOrgRole: string(key.Role), + IsServiceAccount: true, } - err = s.sqlStore.UpdateApikeyServiceAccount(ctx, key.Id, sa.Id) - if err != nil { - return fmt.Errorf("failed to attach new service account to API key for keyId: %d and newServiceAccountId: %d with error: %w", key.Id, sa.Id, err) + newSA, errCreateSA := s.sqlStore.CreateUser(ctx, cmd) + if errCreateSA != nil { + return fmt.Errorf("failed to create service account: %w", errCreateSA) } - s.log.Debug("Updated basic api key", "keyId", key.Id, "newServiceAccountId", sa.Id) + + if errUpdateKey := s.assignApiKeyToServiceAccount(ctx, key.Id, newSA.Id); errUpdateKey != nil { + return fmt.Errorf( + "failed to attach new service account to API key for keyId: %d and newServiceAccountId: %d with error: %w", + key.Id, newSA.Id, errUpdateKey, + ) + } + + s.log.Debug("Updated basic api key", "keyId", key.Id, "newServiceAccountId", newSA.Id) + return nil } @@ -136,9 +153,10 @@ func (s *ServiceAccountsStoreImpl) ListTokens(ctx context.Context, orgID int64, err := s.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { var sess *xorm.Session + quotedUser := s.sqlStore.Dialect.Quote("user") sess = dbSession. - Join("inner", "user", "user.id = api_key.service_account_id"). - Where("user.org_id=? AND user.id=?", orgID, serviceAccountID). + Join("inner", quotedUser, quotedUser+".id = api_key.service_account_id"). + Where(quotedUser+".org_id=? AND "+quotedUser+".id=?", orgID, serviceAccountID). Asc("name") return sess.Find(&result) @@ -146,35 +164,6 @@ func (s *ServiceAccountsStoreImpl) ListTokens(ctx context.Context, orgID int64, return result, err } -func (s *ServiceAccountsStoreImpl) ListServiceAccounts(ctx context.Context, orgID, serviceAccountID int64) ([]*serviceaccounts.ServiceAccountDTO, error) { - query := models.GetOrgUsersQuery{OrgId: orgID, IsServiceAccount: true} - if serviceAccountID > 0 { - query.UserID = serviceAccountID - } - - if err := s.sqlStore.GetOrgUsers(ctx, &query); err != nil { - return nil, err - } - - saDTOs := make([]*serviceaccounts.ServiceAccountDTO, len(query.Result)) - for i, user := range query.Result { - saDTOs[i] = &serviceaccounts.ServiceAccountDTO{ - Id: user.UserId, - OrgId: user.OrgId, - Name: user.Name, - Login: user.Login, - Role: user.Role, - } - tokens, err := s.ListTokens(ctx, user.OrgId, user.UserId) - if err != nil { - return nil, err - } - saDTOs[i].Tokens = int64(len(tokens)) - } - - return saDTOs, nil -} - // RetrieveServiceAccountByID returns a service account by its ID func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) { serviceAccount := &serviceaccounts.ServiceAccountProfileDTO{} @@ -298,8 +287,16 @@ func (s *ServiceAccountsStoreImpl) UpdateServiceAccount(ctx context.Context, return updatedUser, err } -func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, query *models.SearchOrgUsersQuery) ([]*serviceaccounts.ServiceAccountDTO, error) { - serviceAccounts := make([]*serviceaccounts.ServiceAccountDTO, 0) +func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts( + ctx context.Context, orgID int64, query string, page int, limit int, + signedInUser *models.SignedInUser, +) (*serviceaccounts.SearchServiceAccountsResult, error) { + searchResult := &serviceaccounts.SearchServiceAccountsResult{ + TotalCount: 0, + ServiceAccounts: make([]*serviceaccounts.ServiceAccountDTO, 0), + Page: page, + PerPage: limit, + } err := s.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { sess := dbSession.Table("org_user") @@ -309,7 +306,7 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, whereParams := make([]interface{}, 0) whereConditions = append(whereConditions, "org_user.org_id = ?") - whereParams = append(whereParams, query.OrgID) + whereParams = append(whereParams, orgID) whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = %s", @@ -317,7 +314,7 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, s.sqlStore.Dialect.BooleanStr(true))) if s.sqlStore.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) { - acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "serviceaccounts", serviceaccounts.ActionRead) + acFilter, err := accesscontrol.Filter(signedInUser, "org_user.user_id", "serviceaccounts", serviceaccounts.ActionRead) if err != nil { return err } @@ -325,8 +322,8 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, whereParams = append(whereParams, acFilter.Args...) } - if query.Query != "" { - queryWithWildcards := "%" + query.Query + "%" + if query != "" { + queryWithWildcards := "%" + query + "%" whereConditions = append(whereConditions, "(email "+s.sqlStore.Dialect.LikeStr()+" ? OR name "+s.sqlStore.Dialect.LikeStr()+" ? OR login "+s.sqlStore.Dialect.LikeStr()+" ?)") whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards) } @@ -334,9 +331,9 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, if len(whereConditions) > 0 { sess.Where(strings.Join(whereConditions, " AND "), whereParams...) } - if query.Limit > 0 { - offset := query.Limit * (query.Page - 1) - sess.Limit(query.Limit, offset) + if limit > 0 { + offset := limit * (page - 1) + sess.Limit(limit, offset) } sess.Cols( @@ -350,7 +347,7 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, "user.is_disabled", ) sess.Asc("user.email", "user.login") - if err := sess.Find(&serviceAccounts); err != nil { + if err := sess.Find(&searchResult.ServiceAccounts); err != nil { return err } @@ -366,7 +363,7 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, if err != nil { return err } - query.Result.TotalCount = count + searchResult.TotalCount = count return nil }) @@ -374,7 +371,7 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, return nil, err } - return serviceAccounts, nil + return searchResult, nil } func contains(s []int64, e int64) bool { diff --git a/pkg/services/serviceaccounts/database/database_test.go b/pkg/services/serviceaccounts/database/database_test.go index ea1600d46eb..d09aa46de4e 100644 --- a/pkg/services/serviceaccounts/database/database_test.go +++ b/pkg/services/serviceaccounts/database/database_test.go @@ -13,7 +13,7 @@ import ( ) func TestStore_CreateServiceAccount(t *testing.T) { - sqlStore, store := setupTestDatabase(t) + _, store := setupTestDatabase(t) t.Run("create service account", func(t *testing.T) { saDTO, err := store.CreateServiceAccount(context.Background(), 1, "new Service Account") require.NoError(t, err) @@ -21,17 +21,11 @@ func TestStore_CreateServiceAccount(t *testing.T) { assert.Equal(t, "new Service Account", saDTO.Name) assert.Equal(t, 0, int(saDTO.Tokens)) - query := models.GetUserByIdQuery{Id: saDTO.Id} - err = sqlStore.GetUserById(context.Background(), &query) - retrieved := query.Result + retrieved, err := store.RetrieveServiceAccount(context.Background(), 1, saDTO.Id) require.NoError(t, err) assert.Equal(t, "sa-new-service-account", retrieved.Login) assert.Equal(t, "new Service Account", retrieved.Name) - assert.Equal(t, "sa-new-service-account", retrieved.Email) - assert.Equal(t, "", retrieved.Password) assert.Equal(t, 1, int(retrieved.OrgId)) - assert.Len(t, retrieved.Salt, 10) - assert.Equal(t, true, retrieved.IsServiceAccount) }) } diff --git a/pkg/services/serviceaccounts/database/token_store.go b/pkg/services/serviceaccounts/database/token_store.go index e36ab862e46..e69317e55c0 100644 --- a/pkg/services/serviceaccounts/database/token_store.go +++ b/pkg/services/serviceaccounts/database/token_store.go @@ -61,3 +61,27 @@ func (s *ServiceAccountsStoreImpl) DeleteServiceAccountToken(ctx context.Context return nil }) } + +// assignApiKeyToServiceAccount sets the API key service account ID +func (s *ServiceAccountsStoreImpl) assignApiKeyToServiceAccount(ctx context.Context, apikeyId int64, saccountId int64) error { + return s.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + key := models.ApiKey{Id: apikeyId} + exists, err := sess.Get(&key) + if err != nil { + s.log.Warn("API key not loaded", "err", err) + return err + } + if !exists { + s.log.Warn("API key not found", "err", err) + return models.ErrApiKeyNotFound + } + key.ServiceAccountId = &saccountId + + if _, err := sess.ID(key.Id).Update(&key); err != nil { + s.log.Warn("Could not update api key", "err", err) + return err + } + + return nil + }) +} diff --git a/pkg/services/serviceaccounts/models.go b/pkg/services/serviceaccounts/models.go index dc4d33db3ac..5e5a4cf6de1 100644 --- a/pkg/services/serviceaccounts/models.go +++ b/pkg/services/serviceaccounts/models.go @@ -40,6 +40,12 @@ type ServiceAccountDTO struct { AvatarUrl string `json:"avatarUrl"` AccessControl map[string]bool `json:"accessControl,omitempty"` } +type SearchServiceAccountsResult struct { + TotalCount int64 `json:"totalCount"` + ServiceAccounts []*ServiceAccountDTO `json:"serviceAccounts"` + Page int `json:"page"` + PerPage int `json:"perPage"` +} type ServiceAccountProfileDTO struct { Id int64 `json:"id" xorm:"user_id"` diff --git a/pkg/services/serviceaccounts/serviceaccounts.go b/pkg/services/serviceaccounts/serviceaccounts.go index bdf9e65125f..ac95861905e 100644 --- a/pkg/services/serviceaccounts/serviceaccounts.go +++ b/pkg/services/serviceaccounts/serviceaccounts.go @@ -14,9 +14,10 @@ type Service interface { type Store interface { CreateServiceAccount(ctx context.Context, orgID int64, name string) (*ServiceAccountDTO, error) - ListServiceAccounts(ctx context.Context, orgID, serviceAccountID int64) ([]*ServiceAccountDTO, error) - SearchOrgServiceAccounts(ctx context.Context, query *models.SearchOrgUsersQuery) ([]*ServiceAccountDTO, error) - UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64, saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error) + SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, page int, limit int, + signedInUser *models.SignedInUser) (*SearchServiceAccountsResult, error) + UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64, + saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*ServiceAccountProfileDTO, error) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error UpgradeServiceAccounts(ctx context.Context) error diff --git a/pkg/services/serviceaccounts/tests/common.go b/pkg/services/serviceaccounts/tests/common.go index e4363554aa9..59f6077d3fd 100644 --- a/pkg/services/serviceaccounts/tests/common.go +++ b/pkg/services/serviceaccounts/tests/common.go @@ -68,7 +68,6 @@ var _ serviceaccounts.Store = new(ServiceAccountsStoreMock) type Calls struct { CreateServiceAccount []interface{} - ListServiceAccounts []interface{} RetrieveServiceAccount []interface{} DeleteServiceAccount []interface{} UpgradeServiceAccounts []interface{} @@ -110,10 +109,6 @@ func (s *ServiceAccountsStoreMock) ListTokens(ctx context.Context, orgID int64, s.Calls.ListTokens = append(s.Calls.ListTokens, []interface{}{ctx, orgID, serviceAccount}) return nil, nil } -func (s *ServiceAccountsStoreMock) ListServiceAccounts(ctx context.Context, orgID int64, serviceAccountID int64) ([]*serviceaccounts.ServiceAccountDTO, error) { - s.Calls.ListServiceAccounts = append(s.Calls.ListServiceAccounts, []interface{}{ctx, orgID}) - return nil, nil -} func (s *ServiceAccountsStoreMock) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) { s.Calls.RetrieveServiceAccount = append(s.Calls.RetrieveServiceAccount, []interface{}{ctx, orgID, serviceAccountID}) @@ -128,8 +123,9 @@ func (s *ServiceAccountsStoreMock) UpdateServiceAccount(ctx context.Context, return nil, nil } -func (s *ServiceAccountsStoreMock) SearchOrgServiceAccounts(ctx context.Context, query *models.SearchOrgUsersQuery) ([]*serviceaccounts.ServiceAccountDTO, error) { - s.Calls.SearchOrgServiceAccounts = append(s.Calls.SearchOrgServiceAccounts, []interface{}{ctx, query}) +func (s *ServiceAccountsStoreMock) SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, page int, limit int, + user *models.SignedInUser) (*serviceaccounts.SearchServiceAccountsResult, error) { + s.Calls.SearchOrgServiceAccounts = append(s.Calls.SearchOrgServiceAccounts, []interface{}{ctx, orgID, query, page, limit, user}) return nil, nil } diff --git a/pkg/services/sqlstore/apikey.go b/pkg/services/sqlstore/apikey.go index 61037c291d9..eb31a56efaf 100644 --- a/pkg/services/sqlstore/apikey.go +++ b/pkg/services/sqlstore/apikey.go @@ -41,13 +41,12 @@ func (ss *SQLStore) GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuer }) } -// GetAPIKeys queries the database based -// on input on GetApiKeysQuery -func (ss *SQLStore) GetNonServiceAccountAPIKeys(ctx context.Context) []*models.ApiKey { +// GetAllOrgsAPIKeys queries the database for valid non SA APIKeys across all orgs +func (ss *SQLStore) GetAllOrgsAPIKeys(ctx context.Context) []*models.ApiKey { result := make([]*models.ApiKey, 0) err := ss.WithDbSession(ctx, func(dbSession *DBSession) error { sess := dbSession. //CHECK how many API keys do our clients have? Can we load them all? - Where("(expires IS NULL OR expires >= ?) AND service_account_id < 1 ", timeNow().Unix()).Asc("name") + Where("(expires IS NULL OR expires >= ?) AND service_account_id IS NULL", timeNow().Unix()).Asc("name") return sess.Find(&result) }) if err != nil { @@ -114,30 +113,6 @@ func (ss *SQLStore) AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand) }) } -// UpdateApikeyServiceAccount sets a service account for an existing API key -func (ss *SQLStore) UpdateApikeyServiceAccount(ctx context.Context, apikeyId int64, saccountId int64) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - key := models.ApiKey{Id: apikeyId} - exists, err := sess.Get(&key) - if err != nil { - ss.log.Warn("API key not loaded", "err", err) - return err - } - if !exists { - ss.log.Warn("API key not found", "err", err) - return models.ErrApiKeyNotFound - } - key.ServiceAccountId = &saccountId - - if _, err := sess.ID(key.Id).Update(&key); err != nil { - ss.log.Warn("Could not update api key", "err", err) - return err - } - - return nil - }) -} - func (ss *SQLStore) GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error { return ss.WithDbSession(ctx, func(sess *DBSession) error { var apikey models.ApiKey diff --git a/pkg/services/sqlstore/mockstore/mockstore.go b/pkg/services/sqlstore/mockstore/mockstore.go index 49506bfb432..14fe879ee26 100644 --- a/pkg/services/sqlstore/mockstore/mockstore.go +++ b/pkg/services/sqlstore/mockstore/mockstore.go @@ -136,10 +136,6 @@ func (m *SQLStoreMock) DeleteOldLoginAttempts(ctx context.Context, cmd *models.D return m.ExpectedError } -func (m *SQLStoreMock) CreateServiceAccountForApikey(ctx context.Context, orgId int64, keyname string, role models.RoleType) (*models.User, error) { - return nil, m.ExpectedError -} - func (m *SQLStoreMock) CreateUser(ctx context.Context, cmd models.CreateUserCommand) (*models.User, error) { return nil, m.ExpectedError } @@ -595,7 +591,7 @@ func (m *SQLStoreMock) GetAPIKeys(ctx context.Context, query *models.GetApiKeysQ return m.ExpectedError } -func (m *SQLStoreMock) GetNonServiceAccountAPIKeys(ctx context.Context) []*models.ApiKey { +func (m *SQLStoreMock) GetAllOrgsAPIKeys(ctx context.Context) []*models.ApiKey { return nil } @@ -607,10 +603,6 @@ func (m *SQLStoreMock) AddAPIKey(ctx context.Context, cmd *models.AddApiKeyComma return m.ExpectedError } -func (m *SQLStoreMock) UpdateApikeyServiceAccount(ctx context.Context, apikeyId int64, saccountId int64) error { - return m.ExpectedError -} - func (m *SQLStoreMock) GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error { return m.ExpectedError } diff --git a/pkg/services/sqlstore/org_users.go b/pkg/services/sqlstore/org_users.go index 55923cfb24b..7a36bbe7657 100644 --- a/pkg/services/sqlstore/org_users.go +++ b/pkg/services/sqlstore/org_users.go @@ -24,7 +24,7 @@ func (ss *SQLStore) AddOrgUser(ctx context.Context, cmd *models.AddOrgUserComman return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { // check if user exists var user models.User - if exists, err := sess.ID(cmd.UserId).Get(&user); err != nil { + if exists, err := sess.ID(cmd.UserId).Where(notServiceAccountFilter(ss)).Get(&user); err != nil { return err } else if !exists { return models.ErrUserNotFound @@ -114,9 +114,8 @@ func (ss *SQLStore) GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQu whereParams = append(whereParams, query.UserID) } - // TODO: add to chore, for cleaning up after we have created - // service accounts table in the modelling - whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = %t", x.Dialect().Quote("user"), query.IsServiceAccount)) + whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = ?", dialect.Quote("user"))) + whereParams = append(whereParams, dialect.BooleanStr(false)) if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) && query.User != nil { acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "users", accesscontrol.ActionOrgUsersRead) @@ -179,9 +178,7 @@ func (ss *SQLStore) SearchOrgUsers(ctx context.Context, query *models.SearchOrgU whereConditions = append(whereConditions, "org_user.org_id = ?") whereParams = append(whereParams, query.OrgID) - // TODO: add to chore, for cleaning up after we have created - // service accounts table in the modelling - whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = %t", x.Dialect().Quote("user"), query.IsServiceAccount)) + whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = %s", x.Dialect().Quote("user"), ss.Dialect.BooleanStr(false))) if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) { acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "users", accesscontrol.ActionOrgUsersRead) @@ -248,7 +245,7 @@ func (ss *SQLStore) RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgUser return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { // check if user exists var user models.User - if exists, err := sess.ID(cmd.UserId).Get(&user); err != nil { + if exists, err := sess.ID(cmd.UserId).Where(notServiceAccountFilter(ss)).Get(&user); err != nil { return err } else if !exists { return models.ErrUserNotFound @@ -301,7 +298,7 @@ func (ss *SQLStore) RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgUser } } else if cmd.ShouldDeleteOrphanedUser { // no other orgs, delete the full user - if err := deleteUserInTransaction(sess, &models.DeleteUserCommand{UserId: user.Id}); err != nil { + if err := deleteUserInTransaction(ss, sess, &models.DeleteUserCommand{UserId: user.Id}); err != nil { return err } @@ -312,8 +309,8 @@ func (ss *SQLStore) RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgUser }) } +// validate that there is an org admin user left func validateOneAdminLeftInOrg(orgId int64, sess *DBSession) error { - // validate that there is an admin user left res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and role='Admin'", orgId) if err != nil { return err diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index b144bcf017d..d74c7b03df0 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -28,7 +28,6 @@ type Store interface { GetOrgByNameHandler(ctx context.Context, query *models.GetOrgByNameQuery) error CreateLoginAttempt(ctx context.Context, cmd *models.CreateLoginAttemptCommand) error DeleteOldLoginAttempts(ctx context.Context, cmd *models.DeleteOldLoginAttemptsCommand) error - CreateServiceAccountForApikey(ctx context.Context, orgId int64, keyname string, role models.RoleType) (*models.User, error) CreateUser(ctx context.Context, cmd models.CreateUserCommand) (*models.User, error) GetUserById(ctx context.Context, query *models.GetUserByIdQuery) error GetUserByLogin(ctx context.Context, query *models.GetUserByLoginQuery) error @@ -134,10 +133,9 @@ type Store interface { SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuery) error - GetNonServiceAccountAPIKeys(ctx context.Context) []*models.ApiKey + GetAllOrgsAPIKeys(ctx context.Context) []*models.ApiKey DeleteApiKey(ctx context.Context, cmd *models.DeleteApiKeyCommand) error AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand) error - UpdateApikeyServiceAccount(ctx context.Context, apikeyId int64, saccountId int64) error GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error GetApiKeyByName(ctx context.Context, query *models.GetApiKeyByNameQuery) error UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index e6a2349d494..72314b1ba76 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -184,24 +184,6 @@ func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args userCr return user, nil } -func (ss *SQLStore) CreateServiceAccountForApikey(ctx context.Context, orgId int64, keyname string, role models.RoleType) (*models.User, error) { - prefix := "Service-Account-Autogen-" - cmd := models.CreateUserCommand{ - Login: fmt.Sprintf("%v-%v-%v", prefix, orgId, keyname), - Name: prefix + keyname, - OrgId: orgId, - DefaultOrgRole: string(role), - IsServiceAccount: true, - } - - newuser, err := ss.CreateUser(ctx, cmd) - if err != nil { - return nil, fmt.Errorf("failed to create user: %w", err) - } - - return newuser, err -} - func (ss *SQLStore) CreateUser(ctx context.Context, cmd models.CreateUserCommand) (*models.User, error) { var user *models.User err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { @@ -300,10 +282,19 @@ func (ss *SQLStore) CreateUser(ctx context.Context, cmd models.CreateUserCommand return user, err } -func (ss SQLStore) GetUserById(ctx context.Context, query *models.GetUserByIdQuery) error { +func notServiceAccountFilter(ss *SQLStore) string { + return fmt.Sprintf("%s.is_service_account = %s", + ss.Dialect.Quote("user"), + ss.Dialect.BooleanStr(false)) +} + +func (ss *SQLStore) GetUserById(ctx context.Context, query *models.GetUserByIdQuery) error { return ss.WithDbSession(ctx, func(sess *DBSession) error { user := new(models.User) - has, err := sess.ID(query.Id).Get(user) + + has, err := sess.ID(query.Id). + Where(notServiceAccountFilter(ss)). + Get(user) if err != nil { return err @@ -326,7 +317,7 @@ func (ss *SQLStore) GetUserByLogin(ctx context.Context, query *models.GetUserByL // Try and find the user by login first. // It's not sufficient to assume that a LoginOrEmail with an "@" is an email. user := &models.User{Login: query.LoginOrEmail} - has, err := sess.Get(user) + has, err := sess.Where(notServiceAccountFilter(ss)).Get(user) if err != nil { return err @@ -358,7 +349,7 @@ func (ss *SQLStore) GetUserByEmail(ctx context.Context, query *models.GetUserByE } user := &models.User{Email: query.Email} - has, err := sess.Get(user) + has, err := sess.Where(notServiceAccountFilter(ss)).Get(user) if err != nil { return err @@ -382,7 +373,7 @@ func (ss *SQLStore) UpdateUser(ctx context.Context, cmd *models.UpdateUserComman Updated: time.Now(), } - if _, err := sess.ID(cmd.UserId).Update(&user); err != nil { + if _, err := sess.ID(cmd.UserId).Where(notServiceAccountFilter(ss)).Update(&user); err != nil { return err } @@ -405,7 +396,7 @@ func (ss *SQLStore) ChangeUserPassword(ctx context.Context, cmd *models.ChangeUs Updated: time.Now(), } - _, err := sess.ID(cmd.UserId).Update(&user) + _, err := sess.ID(cmd.UserId).Where(notServiceAccountFilter(ss)).Update(&user) return err }) } @@ -456,7 +447,7 @@ func setUsingOrgInTransaction(sess *DBSession, userID int64, orgID int64) error func (ss *SQLStore) GetUserProfile(ctx context.Context, query *models.GetUserProfileQuery) error { return ss.WithDbSession(ctx, func(sess *DBSession) error { var user models.User - has, err := sess.ID(query.UserId).Get(&user) + has, err := sess.ID(query.UserId).Where(notServiceAccountFilter(ss)).Get(&user) if err != nil { return err @@ -506,7 +497,9 @@ func (ss *SQLStore) GetUserOrgList(ctx context.Context, query *models.GetUserOrg query.Result = make([]*models.UserOrgDTO, 0) sess := x.Table("org_user") sess.Join("INNER", "org", "org_user.org_id=org.id") + sess.Join("INNER", x.Dialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user"))) sess.Where("org_user.user_id=?", query.UserId) + sess.Where(notServiceAccountFilter(ss)) sess.Cols("org.name", "org_user.role", "org_user.org_id") sess.OrderBy("org.name") err := sess.Find(&query.Result) @@ -612,8 +605,6 @@ func SearchUsers(ctx context.Context, query *models.SearchUsersQuery) error { whereParams := make([]interface{}, 0) sess := x.Table("user").Alias("u") - // TODO: add to chore, for cleaning up after we have created - // service accounts table in the modelling whereConditions = append(whereConditions, "u.is_service_account = ?") whereParams = append(whereParams, dialect.BooleanStr(false)) @@ -710,7 +701,7 @@ func (ss *SQLStore) DisableUser(ctx context.Context, cmd *models.DisableUserComm user := models.User{} sess := x.Table("user") - if has, err := sess.ID(cmd.UserId).Get(&user); err != nil { + if has, err := sess.ID(cmd.UserId).Where(notServiceAccountFilter(ss)).Get(&user); err != nil { return err } else if !has { return models.ErrUserNotFound @@ -739,32 +730,28 @@ func (ss *SQLStore) BatchDisableUsers(ctx context.Context, cmd *models.BatchDisa disableParams = append(disableParams, v) } - _, err := sess.Exec(disableParams...) - if err != nil { - return err - } - - return nil + _, err := sess.Where(notServiceAccountFilter(ss)).Exec(disableParams...) + return err }) } func (ss *SQLStore) DeleteUser(ctx context.Context, cmd *models.DeleteUserCommand) error { return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - return deleteUserInTransaction(sess, cmd) + return deleteUserInTransaction(ss, sess, cmd) }) } -func deleteUserInTransaction(sess *DBSession, cmd *models.DeleteUserCommand) error { +func deleteUserInTransaction(ss *SQLStore, sess *DBSession, cmd *models.DeleteUserCommand) error { // Check if user exists user := models.User{Id: cmd.UserId} - has, err := sess.Get(&user) + has, err := sess.Where(notServiceAccountFilter(ss)).Get(&user) if err != nil { return err } if !has { return models.ErrUserNotFound } - for _, sql := range userDeletions() { + for _, sql := range UserDeletions() { _, err := sess.Exec(sql, cmd.UserId) if err != nil { return err @@ -773,7 +760,7 @@ func deleteUserInTransaction(sess *DBSession, cmd *models.DeleteUserCommand) err return nil } -func userDeletions() []string { +func UserDeletions() []string { deletes := []string{ "DELETE FROM star WHERE user_id = ?", "DELETE FROM " + dialect.Quote("user") + " WHERE id = ?", @@ -788,18 +775,11 @@ func userDeletions() []string { return deletes } -func ServiceAccountDeletions() []string { - deletes := []string{ - "DELETE FROM api_key WHERE service_account_id = ?", - } - deletes = append(deletes, userDeletions()...) - return deletes -} - +// UpdateUserPermissions sets the user Server Admin flag func (ss *SQLStore) UpdateUserPermissions(userID int64, isAdmin bool) error { return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error { var user models.User - if _, err := sess.ID(userID).Get(&user); err != nil { + if _, err := sess.ID(userID).Where(notServiceAccountFilter(ss)).Get(&user); err != nil { return err } @@ -833,8 +813,8 @@ func (ss *SQLStore) SetUserHelpFlag(ctx context.Context, cmd *models.SetUserHelp }) } +// validateOneAdminLeft validate that there is an admin user left func validateOneAdminLeft(sess *DBSession) error { - // validate that there is an admin user left count, err := sess.Where("is_admin=?", true).Count(&models.User{}) if err != nil { return err