The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/sqlstore/org_test.go

593 lines
20 KiB

package sqlstore
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util"
)
func TestIntegrationAccountDataAccess(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
t.Run("Testing Account DB Access", func(t *testing.T) {
sqlStore := InitTestDB(t)
testUser := &models.SignedInUser{
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionOrgUsersRead: []string{accesscontrol.ScopeUsersAll}},
},
}
t.Run("Given we have organizations, we can query them by IDs", func(t *testing.T) {
var err error
var cmd *models.CreateOrgCommand
ids := []int64{}
for i := 1; i < 4; i++ {
cmd = &models.CreateOrgCommand{Name: fmt.Sprint("Org #", i)}
err = sqlStore.CreateOrg(context.Background(), cmd)
require.NoError(t, err)
ids = append(ids, cmd.Result.Id)
}
query := &models.SearchOrgsQuery{Ids: ids}
err = sqlStore.SearchOrgs(context.Background(), query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 3)
})
t.Run("Given we have organizations, we can limit and paginate search", func(t *testing.T) {
sqlStore = InitTestDB(t)
for i := 1; i < 4; i++ {
cmd := &models.CreateOrgCommand{Name: fmt.Sprint("Org #", i)}
err := sqlStore.CreateOrg(context.Background(), cmd)
require.NoError(t, err)
}
t.Run("Should be able to search with defaults", func(t *testing.T) {
query := &models.SearchOrgsQuery{}
err := sqlStore.SearchOrgs(context.Background(), query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 3)
})
t.Run("Should be able to limit search", func(t *testing.T) {
query := &models.SearchOrgsQuery{Limit: 1}
err := sqlStore.SearchOrgs(context.Background(), query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 1)
})
t.Run("Should be able to limit and paginate search", func(t *testing.T) {
query := &models.SearchOrgsQuery{Limit: 2, Page: 1}
err := sqlStore.SearchOrgs(context.Background(), query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 1)
})
})
t.Run("Given single org mode", func(t *testing.T) {
sqlStore.Cfg.AutoAssignOrg = true
sqlStore.Cfg.AutoAssignOrgId = 1
sqlStore.Cfg.AutoAssignOrgRole = "Viewer"
t.Run("Users should be added to default organization", func(t *testing.T) {
ac1cmd := user.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := user.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
require.NoError(t, err)
ac2, err := sqlStore.CreateUser(context.Background(), ac2cmd)
require.NoError(t, err)
q1 := models.GetUserOrgListQuery{UserId: ac1.ID}
q2 := models.GetUserOrgListQuery{UserId: ac2.ID}
err = sqlStore.GetUserOrgList(context.Background(), &q1)
require.NoError(t, err)
err = sqlStore.GetUserOrgList(context.Background(), &q2)
require.NoError(t, err)
require.Equal(t, q1.Result[0].OrgId, q2.Result[0].OrgId)
require.Equal(t, string(q1.Result[0].Role), "Viewer")
})
})
t.Run("Given single org and 2 users inserted", func(t *testing.T) {
sqlStore = InitTestDB(t)
sqlStore.Cfg.AutoAssignOrg = true
sqlStore.Cfg.AutoAssignOrgId = 1
sqlStore.Cfg.AutoAssignOrgRole = "Viewer"
ac1cmd := user.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := user.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
testUser.OrgId = ac1.OrgID
require.NoError(t, err)
_, err = sqlStore.CreateUser(context.Background(), ac2cmd)
require.NoError(t, err)
t.Run("Can get organization users paginated with query", func(t *testing.T) {
query := models.SearchOrgUsersQuery{
OrgID: ac1.OrgID,
Page: 1,
User: testUser,
}
err = sqlStore.SearchOrgUsers(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result.OrgUsers), 2)
})
t.Run("Can get organization users paginated and limited", func(t *testing.T) {
query := models.SearchOrgUsersQuery{
OrgID: ac1.OrgID,
Limit: 1,
Page: 1,
User: testUser,
}
err = sqlStore.SearchOrgUsers(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result.OrgUsers), 1)
})
})
t.Run("Given two saved users", func(t *testing.T) {
sqlStore = InitTestDB(t)
sqlStore.Cfg.AutoAssignOrg = false
ac1cmd := user.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := user.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true}
serviceaccountcmd := user.CreateUserCommand{Login: "serviceaccount", Email: "service@test.com", Name: "serviceaccount name", IsAdmin: true, IsServiceAccount: true}
ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
require.NoError(t, err)
ac2, err := sqlStore.CreateUser(context.Background(), ac2cmd)
require.NoError(t, err)
// user only used for making sure we filter out the service accounts
_, err = sqlStore.CreateUser(context.Background(), serviceaccountcmd)
require.NoError(t, err)
t.Run("Should be able to read user info projection", func(t *testing.T) {
query := models.GetUserProfileQuery{UserId: ac1.ID}
err = sqlStore.GetUserProfile(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, query.Result.Email, "ac1@test.com")
require.Equal(t, query.Result.Login, "ac1")
})
t.Run("Can search users", func(t *testing.T) {
query := models.SearchUsersQuery{Query: "", SignedInUser: &models.SignedInUser{
OrgId: 1,
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionUsersRead: {accesscontrol.ScopeGlobalUsersAll}},
},
}}
err := sqlStore.SearchUsers(context.Background(), &query)
require.NoError(t, err)
require.Len(t, query.Result.Users, 2)
require.Equal(t, query.Result.Users[0].Email, "ac1@test.com")
require.Equal(t, query.Result.Users[1].Email, "ac2@test.com")
})
t.Run("Given an added org user", func(t *testing.T) {
cmd := models.AddOrgUserCommand{
OrgId: ac1.OrgID,
UserId: ac2.ID,
Role: models.ROLE_VIEWER,
}
err := sqlStore.AddOrgUser(context.Background(), &cmd)
t.Run("Should have been saved without error", func(t *testing.T) {
require.NoError(t, err)
})
t.Run("Can update org user role", func(t *testing.T) {
updateCmd := models.UpdateOrgUserCommand{OrgId: ac1.OrgID, UserId: ac2.ID, Role: models.ROLE_ADMIN}
err = sqlStore.UpdateOrgUser(context.Background(), &updateCmd)
require.NoError(t, err)
orgUsersQuery := models.GetOrgUsersQuery{
OrgId: ac1.OrgID,
User: &models.SignedInUser{
OrgId: ac1.OrgID,
Permissions: map[int64]map[string][]string{ac1.OrgID: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}}},
},
}
err = sqlStore.GetOrgUsers(context.Background(), &orgUsersQuery)
require.NoError(t, err)
require.EqualValues(t, orgUsersQuery.Result[1].Role, models.ROLE_ADMIN)
})
t.Run("Can get logged in user projection", func(t *testing.T) {
query := models.GetSignedInUserQuery{UserId: ac2.ID}
err := sqlStore.GetSignedInUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, query.Result.Email, "ac2@test.com")
require.Equal(t, query.Result.OrgId, ac2.OrgID)
require.Equal(t, query.Result.Name, "ac2 name")
require.Equal(t, query.Result.Login, "ac2")
require.EqualValues(t, query.Result.OrgRole, "Admin")
require.Equal(t, query.Result.OrgName, "ac2@test.com")
require.Equal(t, query.Result.IsGrafanaAdmin, true)
})
t.Run("Can get user organizations", func(t *testing.T) {
query := models.GetUserOrgListQuery{UserId: ac2.ID}
err := sqlStore.GetUserOrgList(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
})
t.Run("Can get organization users", func(t *testing.T) {
query := models.GetOrgUsersQuery{
OrgId: ac1.OrgID,
User: &models.SignedInUser{
OrgId: ac1.OrgID,
Permissions: map[int64]map[string][]string{ac1.OrgID: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}}},
},
}
err := sqlStore.GetOrgUsers(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
require.Equal(t, query.Result[0].Role, "Admin")
})
t.Run("Can get organization users with query", func(t *testing.T) {
query := models.GetOrgUsersQuery{
OrgId: ac1.OrgID,
Query: "ac1",
User: &models.SignedInUser{
OrgId: ac1.OrgID,
Permissions: map[int64]map[string][]string{ac1.OrgID: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}}},
},
}
err := sqlStore.GetOrgUsers(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 1)
require.Equal(t, query.Result[0].Email, ac1.Email)
})
t.Run("Can get organization users with query and limit", func(t *testing.T) {
query := models.GetOrgUsersQuery{
OrgId: ac1.OrgID,
Query: "ac",
Limit: 1,
User: &models.SignedInUser{
OrgId: ac1.OrgID,
Permissions: map[int64]map[string][]string{ac1.OrgID: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}}},
},
}
err := sqlStore.GetOrgUsers(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 1)
require.Equal(t, query.Result[0].Email, ac1.Email)
})
t.Run("Can set using org", func(t *testing.T) {
cmd := models.SetUsingOrgCommand{UserId: ac2.ID, OrgId: ac1.OrgID}
err := sqlStore.SetUsingOrg(context.Background(), &cmd)
require.NoError(t, err)
t.Run("SignedInUserQuery with a different org", func(t *testing.T) {
query := models.GetSignedInUserQuery{UserId: ac2.ID}
err := sqlStore.GetSignedInUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, query.Result.OrgId, ac1.OrgID)
require.Equal(t, query.Result.Email, "ac2@test.com")
require.Equal(t, query.Result.Name, "ac2 name")
require.Equal(t, query.Result.Login, "ac2")
require.Equal(t, query.Result.OrgName, "ac1@test.com")
})
t.Run("Should set last org as current when removing user from current", func(t *testing.T) {
remCmd := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac2.ID}
err := sqlStore.RemoveOrgUser(context.Background(), &remCmd)
require.NoError(t, err)
query := models.GetSignedInUserQuery{UserId: ac2.ID}
err = sqlStore.GetSignedInUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, query.Result.OrgId, ac2.OrgID)
})
})
t.Run("Removing user from org should delete user completely if in no other org", func(t *testing.T) {
// make sure ac2 has no org
err := sqlStore.DeleteOrg(context.Background(), &models.DeleteOrgCommand{Id: ac2.OrgID})
require.NoError(t, err)
// remove ac2 user from ac1 org
remCmd := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac2.ID, ShouldDeleteOrphanedUser: true}
err = sqlStore.RemoveOrgUser(context.Background(), &remCmd)
require.NoError(t, err)
require.True(t, remCmd.UserWasDeleted)
err = sqlStore.GetSignedInUser(context.Background(), &models.GetSignedInUserQuery{UserId: ac2.ID})
require.Equal(t, err, models.ErrUserNotFound)
})
t.Run("Cannot delete last admin org user", func(t *testing.T) {
cmd := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac1.ID}
err := sqlStore.RemoveOrgUser(context.Background(), &cmd)
require.Equal(t, err, models.ErrLastOrgAdmin)
})
t.Run("Cannot update role so no one is admin user", func(t *testing.T) {
cmd := models.UpdateOrgUserCommand{OrgId: ac1.OrgID, UserId: ac1.ID, Role: models.ROLE_VIEWER}
err := sqlStore.UpdateOrgUser(context.Background(), &cmd)
require.Equal(t, err, models.ErrLastOrgAdmin)
})
t.Run("Given an org user with dashboard permissions", func(t *testing.T) {
ac3cmd := user.CreateUserCommand{Login: "ac3", Email: "ac3@test.com", Name: "ac3 name", IsAdmin: false}
ac3, err := sqlStore.CreateUser(context.Background(), ac3cmd)
require.NoError(t, err)
orgUserCmd := models.AddOrgUserCommand{
OrgId: ac1.OrgID,
UserId: ac3.ID,
Role: models.ROLE_VIEWER,
}
err = sqlStore.AddOrgUser(context.Background(), &orgUserCmd)
require.NoError(t, err)
query := models.GetOrgUsersQuery{
OrgId: ac1.OrgID,
User: &models.SignedInUser{
OrgId: ac1.OrgID,
Permissions: map[int64]map[string][]string{ac1.OrgID: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}}},
},
}
err = sqlStore.GetOrgUsers(context.Background(), &query)
require.NoError(t, err)
// require.Equal(t, len(query.Result), 3)
dash1 := insertTestDashboard(t, sqlStore, "1 test dash", ac1.OrgID, 0, false, "prod", "webapp")
dash2 := insertTestDashboard(t, sqlStore, "2 test dash", ac3.OrgID, 0, false, "prod", "webapp")
err = updateDashboardAcl(t, sqlStore, dash1.Id, &models.DashboardAcl{
DashboardID: dash1.Id, OrgID: ac1.OrgID, UserID: ac3.ID, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
err = updateDashboardAcl(t, sqlStore, dash2.Id, &models.DashboardAcl{
DashboardID: dash2.Id, OrgID: ac3.OrgID, UserID: ac3.ID, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
t.Run("When org user is deleted", func(t *testing.T) {
cmdRemove := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac3.ID}
err := sqlStore.RemoveOrgUser(context.Background(), &cmdRemove)
require.NoError(t, err)
t.Run("Should remove dependent permissions for deleted org user", func(t *testing.T) {
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: dash1.Id, OrgID: ac1.OrgID}
err = getDashboardAclInfoList(sqlStore, permQuery)
require.NoError(t, err)
require.Equal(t, len(permQuery.Result), 0)
})
t.Run("Should not remove dashboard permissions for same user in another org", func(t *testing.T) {
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: dash2.Id, OrgID: ac3.OrgID}
err = getDashboardAclInfoList(sqlStore, permQuery)
require.NoError(t, err)
require.Equal(t, len(permQuery.Result), 1)
require.Equal(t, permQuery.Result[0].OrgId, ac3.OrgID)
require.Equal(t, permQuery.Result[0].UserId, ac3.ID)
})
})
})
})
})
})
}
//TODO: Use FakeDashboardStore when org has its own service
func insertTestDashboard(t *testing.T, sqlStore *SQLStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
var dash *models.Dashboard
err := sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
dash = cmd.GetDashboardModel()
dash.SetVersion(1)
dash.Created = time.Now()
dash.Updated = time.Now()
dash.Uid = util.GenerateShortUID()
_, err := sess.Insert(dash)
return err
})
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.Id)
dash.Data.Set("uid", dash.Uid)
err = sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
dashVersion := &dashver.DashboardVersion{
DashboardID: dash.Id,
ParentVersion: dash.Version,
RestoredFrom: cmd.RestoredFrom,
Version: dash.Version,
Created: time.Now(),
CreatedBy: dash.UpdatedBy,
Message: cmd.Message,
Data: dash.Data,
}
require.NoError(t, err)
if affectedRows, err := sess.Insert(dashVersion); err != nil {
return err
} else if affectedRows == 0 {
return dashboards.ErrDashboardNotFound
}
return nil
})
require.NoError(t, err)
return dash
}
//TODO: Use FakeDashboardStore when org has its own service
func updateDashboardAcl(t *testing.T, sqlStore *SQLStore, dashboardID int64, items ...*models.DashboardAcl) error {
t.Helper()
err := sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", dashboardID)
if err != nil {
return fmt.Errorf("deleting from dashboard_acl failed: %w", err)
}
for _, item := range items {
item.Created = time.Now()
item.Updated = time.Now()
if item.UserID == 0 && item.TeamID == 0 && (item.Role == nil || !item.Role.IsValid()) {
return models.ErrDashboardAclInfoMissing
}
if item.DashboardID == 0 {
return models.ErrDashboardPermissionDashboardEmpty
}
sess.Nullable("user_id", "team_id")
if _, err := sess.Insert(item); err != nil {
return err
}
}
// Update dashboard HasAcl flag
dashboard := models.Dashboard{HasAcl: true}
_, err = sess.Cols("has_acl").Where("id=?", dashboardID).Update(&dashboard)
return err
})
return err
}
// This function was copied from pkg/services/dashboards/database to circumvent
// import cycles. When this org-related code is refactored into a service the
// tests can the real GetDashboardAclInfoList functions
func getDashboardAclInfoList(s *SQLStore, query *models.GetDashboardAclInfoListQuery) error {
outerErr := s.WithDbSession(context.Background(), func(dbSession *DBSession) error {
query.Result = make([]*models.DashboardAclInfoDTO, 0)
falseStr := dialect.BooleanStr(false)
if query.DashboardID == 0 {
sql := `SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
'' as user_login,
'' as user_email,
'' as team,
'' as title,
'' as slug,
'' as uid,` +
falseStr + ` AS is_folder,` +
falseStr + ` AS inherited
FROM dashboard_acl as da
WHERE da.dashboard_id = -1`
return dbSession.SQL(sql).Find(&query.Result)
}
rawSQL := `
-- get permissions for the dashboard and its parent folder
SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
u.login AS user_login,
u.email AS user_email,
ug.name AS team,
ug.email AS team_email,
d.title,
d.slug,
d.uid,
d.is_folder,
CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
FROM dashboard as d
LEFT JOIN dashboard folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id OR
(
-- include default permissions -->
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
LEFT JOIN team ug on ug.id = da.team_id
WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
ORDER BY da.id ASC
`
return dbSession.SQL(rawSQL, query.OrgID, query.DashboardID).Find(&query.Result)
})
if outerErr != nil {
return outerErr
}
for _, p := range query.Result {
p.PermissionName = p.Permission.String()
}
return nil
}