mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Service accounts: Split user and service account database (#46442)
* ServiceAccounts: remove unused endpoint * ServiceAccounts: remove usage of getOrgUsers from service accounts * use dialect for boolean str true in delete * return service account results directly * Move Service Account Deletions to sa package Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: gamab <gabi.mabs@gmail.com> * Move service account methods to service accounts Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: gamab <gabi.mabs@gmail.com> * Service accounts should not interfere with users Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * filter service accounts in user services * mispell fix * fix overextended lines Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * fix variable Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: gamab <gabi.mabs@gmail.com>
This commit is contained in:
parent
99b4dfc27d
commit
ef9fe26886
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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"`
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user