LDAP: Add API endpoint to debug user mapping from LDAP (#18833)

* Move the ReloadLDAPCfg function to the debug file

Appears to be a better suite place for this.

* LDAP: Return the server information when we find a specific user

We allow you to specify multiple LDAP servers as part of LDAP authentication integration. As part of searching for specific users, we need to understand from which server they come from. Returning the server configuration as part of the search will help us do two things:

- Understand in which server we found the user
- Have access the groups specified as part of the server configuration

* LDAP: Adds the /api/admin/ldap/:username endpoint

This endpoint returns a user found within the configured LDAP server(s). Moreso, it provides the mapping information for the user to help administrators understand how the users would be created within Grafana based on the current configuration.

No changes are executed or saved to the database, this is all an in-memory representation of how the final result would look like.
This commit is contained in:
gotjosh
2019-09-03 18:34:44 +01:00
committed by GitHub
parent 09032cd766
commit 0cbbb43222
15 changed files with 478 additions and 48 deletions

View File

@ -1,17 +0,0 @@
package api
import (
"github.com/grafana/grafana/pkg/services/ldap"
)
func (server *HTTPServer) ReloadLDAPCfg() Response {
if !ldap.IsEnabled() {
return Error(400, "LDAP is not enabled", nil)
}
err := ldap.ReloadConfig()
if err != nil {
return Error(500, "Failed to reload ldap config.", err)
}
return Success("LDAP config reloaded")
}

View File

@ -395,6 +395,7 @@ func (hs *HTTPServer) registerRoutes() {
adminRoute.Post("/provisioning/datasources/reload", Wrap(hs.AdminProvisioningReloadDatasources)) adminRoute.Post("/provisioning/datasources/reload", Wrap(hs.AdminProvisioningReloadDatasources))
adminRoute.Post("/provisioning/notifications/reload", Wrap(hs.AdminProvisioningReloadNotifications)) adminRoute.Post("/provisioning/notifications/reload", Wrap(hs.AdminProvisioningReloadNotifications))
adminRoute.Post("/ldap/reload", Wrap(hs.ReloadLDAPCfg)) adminRoute.Post("/ldap/reload", Wrap(hs.ReloadLDAPCfg))
adminRoute.Get("/ldap/:username", Wrap(hs.GetUserFromLDAP))
}, reqGrafanaAdmin) }, reqGrafanaAdmin)
// rendering // rendering

183
pkg/api/ldap_debug.go Normal file
View File

@ -0,0 +1,183 @@
package api
import (
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/util"
)
var (
getLDAPConfig = multildap.GetConfig
newLDAP = multildap.New
errOrganizationNotFound = func(orgId int64) error {
return fmt.Errorf("Unable to find organization with ID '%d'", orgId)
}
)
// LDAPAttribute is a serializer for user attributes mapped from LDAP. Is meant to display both the serialized value and the LDAP key we received it from.
type LDAPAttribute struct {
ConfigAttributeValue string `json:"cfgAttrValue"`
LDAPAttributeValue string `json:"ldapValue"`
}
// RoleDTO is a serializer for mapped roles from LDAP
type RoleDTO struct {
OrgId int64 `json:"orgId"`
OrgName string `json:"orgName"`
OrgRole models.RoleType `json:"orgRole"`
GroupDN string `json:"groupDN"`
}
// TeamDTO is a serializer for mapped Teams from LDAP
type TeamDTO struct {
GroupDN string `json:"groupDN"`
TeamId int64 `json:"teamId"`
TeamName string `json:"teamName"`
}
// LDAPUserDTO is a serializer for users mapped from LDAP
type LDAPUserDTO struct {
Name *LDAPAttribute `json:"name"`
Surname *LDAPAttribute `json:"surname"`
Email *LDAPAttribute `json:"email"`
Username *LDAPAttribute `json:"login"`
IsGrafanaAdmin *bool `json:"isGrafanaAdmin"`
IsDisabled bool `json:"isDisabled"`
OrgRoles []RoleDTO `json:"roles"`
Teams []TeamDTO `json:"teams"`
}
// FetchOrgs fetches the organization(s) information by executing a single query to the database. Then, populating the DTO with the information retrieved.
func (user *LDAPUserDTO) FetchOrgs() error {
orgIds := []int64{}
for _, or := range user.OrgRoles {
orgIds = append(orgIds, or.OrgId)
}
q := &models.SearchOrgsQuery{}
q.Ids = orgIds
if err := bus.Dispatch(q); err != nil {
return err
}
orgNamesById := map[int64]string{}
for _, org := range q.Result {
orgNamesById[org.Id] = org.Name
}
for i, orgDTO := range user.OrgRoles {
orgName := orgNamesById[orgDTO.OrgId]
if orgName != "" {
user.OrgRoles[i].OrgName = orgName
} else {
return errOrganizationNotFound(orgDTO.OrgId)
}
}
return nil
}
// ReloadLDAPCfg reloads the LDAP configuration
func (server *HTTPServer) ReloadLDAPCfg() Response {
if !ldap.IsEnabled() {
return Error(400, "LDAP is not enabled", nil)
}
err := ldap.ReloadConfig()
if err != nil {
return Error(500, "Failed to reload ldap config.", err)
}
return Success("LDAP config reloaded")
}
// GetUserFromLDAP finds an user based on a username in LDAP. This helps illustrate how would the particular user be mapped in Grafana when synced.
func (server *HTTPServer) GetUserFromLDAP(c *models.ReqContext) Response {
ldapConfig, err := getLDAPConfig()
if err != nil {
return Error(400, "Failed to obtain the LDAP configuration. Please ", err)
}
ldap := newLDAP(ldapConfig.Servers)
username := c.Params(":username")
if len(username) == 0 {
return Error(http.StatusBadRequest, "Validation error. You must specify an username", nil)
}
user, serverConfig, err := ldap.User(username)
if user == nil {
return Error(http.StatusNotFound, "No user was found on the LDAP server(s)", err)
}
name, surname := splitName(user.Name)
u := &LDAPUserDTO{
Name: &LDAPAttribute{serverConfig.Attr.Name, name},
Surname: &LDAPAttribute{serverConfig.Attr.Surname, surname},
Email: &LDAPAttribute{serverConfig.Attr.Email, user.Email},
Username: &LDAPAttribute{serverConfig.Attr.Username, user.Login},
IsGrafanaAdmin: user.IsGrafanaAdmin,
IsDisabled: user.IsDisabled,
}
orgRoles := []RoleDTO{}
for _, g := range serverConfig.Groups {
role := &RoleDTO{}
if isMatchToLDAPGroup(user, g) {
role.OrgId = g.OrgID
role.OrgRole = user.OrgRoles[g.OrgID]
role.GroupDN = g.GroupDN
orgRoles = append(orgRoles, *role)
} else {
role.OrgId = g.OrgID
role.GroupDN = g.GroupDN
orgRoles = append(orgRoles, *role)
}
}
u.OrgRoles = orgRoles
err = u.FetchOrgs()
if err != nil {
return Error(http.StatusBadRequest, "Organization not found - Please verify your LDAP configuration", err)
}
return JSON(200, u)
}
// isMatchToLDAPGroup determines if we were able to match an LDAP group to an organization+role.
// Since we allow one role per organization. If it's set, we were able to match it.
func isMatchToLDAPGroup(user *models.ExternalUserInfo, groupConfig *ldap.GroupToOrgRole) bool {
return user.OrgRoles[groupConfig.OrgID] == groupConfig.OrgRole
}
// splitName receives the full name of a user and splits it into two parts: A name and a surname.
func splitName(name string) (string, string) {
names := util.SplitString(name)
switch len(names) {
case 0:
return "", ""
case 1:
return names[0], ""
default:
return names[0], names[1]
}
}

223
pkg/api/ldap_debug_test.go Normal file
View File

@ -0,0 +1,223 @@
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type LDAPMock struct {
Results []*models.ExternalUserInfo
}
var userSearchResult *models.ExternalUserInfo
var userSearchConfig ldap.ServerConfig
func (m *LDAPMock) Login(query *models.LoginUserQuery) (*models.ExternalUserInfo, error) {
return &models.ExternalUserInfo{}, nil
}
func (m *LDAPMock) Users(logins []string) ([]*models.ExternalUserInfo, error) {
s := []*models.ExternalUserInfo{}
return s, nil
}
func (m *LDAPMock) User(login string) (*models.ExternalUserInfo, ldap.ServerConfig, error) {
return userSearchResult, userSearchConfig, nil
}
func getUserFromLDAPContext(t *testing.T, requestURL string) *scenarioContext {
t.Helper()
sc := setupScenarioContext(requestURL)
hs := &HTTPServer{Cfg: setting.NewCfg()}
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c
return hs.GetUserFromLDAP(c)
})
sc.m.Get("/api/admin/ldap/:username", sc.defaultHandler)
sc.resp = httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, requestURL, nil)
sc.req = req
sc.exec()
return sc
}
func TestGetUserFromLDAPApiEndpoint_UserNotFound(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) {
return &ldap.Config{}, nil
}
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
return &LDAPMock{}
}
userSearchResult = nil
sc := getUserFromLDAPContext(t, "/api/admin/ldap/user-that-does-not-exist")
require.Equal(t, sc.resp.Code, http.StatusNotFound)
responseString, err := getBody(sc.resp)
assert.Nil(t, err)
assert.Equal(t, "{\"message\":\"No user was found on the LDAP server(s)\"}", responseString)
}
func TestGetUserFromLDAPApiEndpoint_OrgNotfound(t *testing.T) {
isAdmin := true
userSearchResult = &models.ExternalUserInfo{
Name: "John Doe",
Email: "john.doe@example.com",
Login: "johndoe",
OrgRoles: map[int64]models.RoleType{1: models.ROLE_ADMIN, 2: models.ROLE_VIEWER},
IsGrafanaAdmin: &isAdmin,
}
userSearchConfig = ldap.ServerConfig{
Attr: ldap.AttributeMap{
Name: "ldap-name",
Surname: "ldap-surname",
Email: "ldap-email",
Username: "ldap-username",
},
Groups: []*ldap.GroupToOrgRole{
{
GroupDN: "cn=admins,ou=groups,dc=grafana,dc=org",
OrgID: 1,
OrgRole: models.ROLE_ADMIN,
},
{
GroupDN: "cn=admins,ou=groups,dc=grafana2,dc=org",
OrgID: 2,
OrgRole: models.ROLE_VIEWER,
},
},
}
mockOrgSearchResult := []*models.OrgDTO{
{Id: 1, Name: "Main Org."},
}
bus.AddHandler("test", func(query *models.SearchOrgsQuery) error {
query.Result = mockOrgSearchResult
return nil
})
getLDAPConfig = func() (*ldap.Config, error) {
return &ldap.Config{}, nil
}
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
return &LDAPMock{}
}
sc := getUserFromLDAPContext(t, "/api/admin/ldap/johndoe")
require.Equal(t, sc.resp.Code, http.StatusBadRequest)
jsonResponse, err := getJSONbody(sc.resp)
assert.Nil(t, err)
expected := `
{
"error": "Unable to find organization with ID '2'",
"message": "Organization not found - Please verify your LDAP configuration"
}
`
var expectedJSON interface{}
_ = json.Unmarshal([]byte(expected), &expectedJSON)
assert.Equal(t, jsonResponse, expectedJSON)
}
func TestGetUserFromLDAPApiEndpoint(t *testing.T) {
isAdmin := true
userSearchResult = &models.ExternalUserInfo{
Name: "John Doe",
Email: "john.doe@example.com",
Login: "johndoe",
OrgRoles: map[int64]models.RoleType{1: models.ROLE_ADMIN},
IsGrafanaAdmin: &isAdmin,
}
userSearchConfig = ldap.ServerConfig{
Attr: ldap.AttributeMap{
Name: "ldap-name",
Surname: "ldap-surname",
Email: "ldap-email",
Username: "ldap-username",
},
Groups: []*ldap.GroupToOrgRole{
{
GroupDN: "cn=admins,ou=groups,dc=grafana,dc=org",
OrgID: 1,
OrgRole: models.ROLE_ADMIN,
},
},
}
mockOrgSearchResult := []*models.OrgDTO{
{Id: 1, Name: "Main Org."},
}
bus.AddHandler("test", func(query *models.SearchOrgsQuery) error {
query.Result = mockOrgSearchResult
return nil
})
getLDAPConfig = func() (*ldap.Config, error) {
return &ldap.Config{}, nil
}
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
return &LDAPMock{}
}
sc := getUserFromLDAPContext(t, "/api/admin/ldap/johndoe")
require.Equal(t, sc.resp.Code, http.StatusOK)
jsonResponse, err := getJSONbody(sc.resp)
assert.Nil(t, err)
expected := `
{
"name": {
"cfgAttrValue": "ldap-name", "ldapValue": "John"
},
"surname": {
"cfgAttrValue": "ldap-surname", "ldapValue": "Doe"
},
"email": {
"cfgAttrValue": "ldap-email", "ldapValue": "john.doe@example.com"
},
"login": {
"cfgAttrValue": "ldap-username", "ldapValue": "johndoe"
},
"isGrafanaAdmin": true,
"isDisabled": false,
"roles": [
{ "orgId": 1, "orgRole": "Admin", "orgName": "Main Org.", "groupDN": "cn=admins,ou=groups,dc=grafana,dc=org" }
],
"teams": null
}
`
var expectedJSON interface{}
_ = json.Unmarshal([]byte(expected), &expectedJSON)
assert.Equal(t, jsonResponse, expectedJSON)
}

View File

@ -2,17 +2,19 @@ package api
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json"
"errors" "errors"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
"testing" "testing"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
) )
func mockSetIndexViewData() { func mockSetIndexViewData() {
@ -50,6 +52,18 @@ func getBody(resp *httptest.ResponseRecorder) (string, error) {
return string(responseData), nil return string(responseData), nil
} }
func getJSONbody(resp *httptest.ResponseRecorder) (interface{}, error) {
var j interface{}
err := json.Unmarshal(resp.Body.Bytes(), &j)
if err != nil {
return nil, err
}
return j, nil
}
func TestLoginErrorCookieApiEndpoint(t *testing.T) { func TestLoginErrorCookieApiEndpoint(t *testing.T) {
mockSetIndexViewData() mockSetIndexViewData()
defer resetSetIndexViewData() defer resetSetIndexViewData()

View File

@ -4,12 +4,11 @@ import (
"errors" "errors"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap" "github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
) )
var errTest = errors.New("Test error") var errTest = errors.New("Test error")
@ -98,9 +97,10 @@ func (auth *mockAuth) Users(logins []string) (
func (auth *mockAuth) User(login string) ( func (auth *mockAuth) User(login string) (
*models.ExternalUserInfo, *models.ExternalUserInfo,
ldap.ServerConfig,
error, error,
) { ) {
return nil, nil return nil, ldap.ServerConfig{}, nil
} }
func (auth *mockAuth) Add(dn string, values map[string][]string) error { func (auth *mockAuth) Add(dn string, values map[string][]string) error {

View File

@ -218,7 +218,7 @@ func (auth *AuthProxy) LoginViaLDAP() (int64, *Error) {
return 0, newError("Failed to get LDAP config", nil) return 0, newError("Failed to get LDAP config", nil)
} }
extUser, err := newLDAP(config.Servers).User(auth.header) extUser, _, err := newLDAP(config.Servers).User(auth.header)
if err != nil { if err != nil {
return 0, newError(err.Error(), nil) return 0, newError(err.Error(), nil)
} }

View File

@ -36,13 +36,14 @@ func (stub *TestMultiLDAP) Login(query *models.LoginUserQuery) (
func (stub *TestMultiLDAP) User(login string) ( func (stub *TestMultiLDAP) User(login string) (
*models.ExternalUserInfo, *models.ExternalUserInfo,
ldap.ServerConfig,
error, error,
) { ) {
stub.userCalled = true stub.userCalled = true
result := &models.ExternalUserInfo{ result := &models.ExternalUserInfo{
UserId: stub.ID, UserId: stub.ID,
} }
return result, nil return result, ldap.ServerConfig{}, nil
} }
func prepareMiddleware(t *testing.T, req *http.Request, store *remotecache.RemoteCache) *AuthProxy { func prepareMiddleware(t *testing.T, req *http.Request, store *remotecache.RemoteCache) *AuthProxy {

View File

@ -67,6 +67,7 @@ type SearchOrgsQuery struct {
Name string Name string
Limit int Limit int
Page int Page int
Ids []int64
Result []*OrgDTO Result []*OrgDTO
} }

View File

@ -10,10 +10,9 @@ import (
"strings" "strings"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"gopkg.in/ldap.v3"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"gopkg.in/ldap.v3"
) )
// IConnection is interface for LDAP connection manipulation // IConnection is interface for LDAP connection manipulation

View File

@ -39,7 +39,7 @@ type IMultiLDAP interface {
) )
User(login string) ( User(login string) (
*models.ExternalUserInfo, error, *models.ExternalUserInfo, ldap.ServerConfig, error,
) )
} }
@ -92,14 +92,15 @@ func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) (
return nil, ErrInvalidCredentials return nil, ErrInvalidCredentials
} }
// User gets a user by login // User attempts to find an user by login/username by searching into all of the configured LDAP servers. Then, if the user is found it returns the user alongisde the server it was found.
func (multiples *MultiLDAP) User(login string) ( func (multiples *MultiLDAP) User(login string) (
*models.ExternalUserInfo, *models.ExternalUserInfo,
ldap.ServerConfig,
error, error,
) { ) {
if len(multiples.configs) == 0 { if len(multiples.configs) == 0 {
return nil, ErrNoLDAPServers return nil, ldap.ServerConfig{}, ErrNoLDAPServers
} }
search := []string{login} search := []string{login}
@ -107,26 +108,26 @@ func (multiples *MultiLDAP) User(login string) (
server := newLDAP(config) server := newLDAP(config)
if err := server.Dial(); err != nil { if err := server.Dial(); err != nil {
return nil, err return nil, *config, err
} }
defer server.Close() defer server.Close()
if err := server.Bind(); err != nil { if err := server.Bind(); err != nil {
return nil, err return nil, *config, err
} }
users, err := server.Users(search) users, err := server.Users(search)
if err != nil { if err != nil {
return nil, err return nil, *config, err
} }
if len(users) != 0 { if len(users) != 0 {
return users[0], nil return users[0], *config, nil
} }
} }
return nil, ErrDidNotFindUser return nil, ldap.ServerConfig{}, ErrDidNotFindUser
} }
// Users gets users from multiple LDAP servers // Users gets users from multiple LDAP servers

View File

@ -4,10 +4,9 @@ import (
"errors" "errors"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
. "github.com/smartystreets/goconvey/convey"
) )
func TestMultiLDAP(t *testing.T) { func TestMultiLDAP(t *testing.T) {
@ -127,7 +126,7 @@ func TestMultiLDAP(t *testing.T) {
setup() setup()
multi := New([]*ldap.ServerConfig{}) multi := New([]*ldap.ServerConfig{})
_, err := multi.User("test") _, _, err := multi.User("test")
So(err, ShouldBeError) So(err, ShouldBeError)
So(err, ShouldEqual, ErrNoLDAPServers) So(err, ShouldEqual, ErrNoLDAPServers)
@ -145,7 +144,7 @@ func TestMultiLDAP(t *testing.T) {
{}, {}, {}, {},
}) })
_, err := multi.User("test") _, _, err := multi.User("test")
So(err, ShouldBeError) So(err, ShouldBeError)
So(err, ShouldEqual, expected) So(err, ShouldEqual, expected)
@ -159,7 +158,7 @@ func TestMultiLDAP(t *testing.T) {
multi := New([]*ldap.ServerConfig{ multi := New([]*ldap.ServerConfig{
{}, {}, {}, {},
}) })
_, err := multi.User("test") _, _, err := multi.User("test")
So(mock.dialCalledTimes, ShouldEqual, 2) So(mock.dialCalledTimes, ShouldEqual, 2)
So(mock.usersCalledTimes, ShouldEqual, 2) So(mock.usersCalledTimes, ShouldEqual, 2)
@ -179,7 +178,7 @@ func TestMultiLDAP(t *testing.T) {
multi := New([]*ldap.ServerConfig{ multi := New([]*ldap.ServerConfig{
{}, {}, {}, {},
}) })
_, err := multi.User("test") _, _, err := multi.User("test")
So(mock.dialCalledTimes, ShouldEqual, 1) So(mock.dialCalledTimes, ShouldEqual, 1)
So(mock.usersCalledTimes, ShouldEqual, 1) So(mock.usersCalledTimes, ShouldEqual, 1)
@ -206,7 +205,7 @@ func TestMultiLDAP(t *testing.T) {
multi := New([]*ldap.ServerConfig{ multi := New([]*ldap.ServerConfig{
{}, {}, {}, {},
}) })
user, err := multi.User("test") user, _, err := multi.User("test")
So(mock.dialCalledTimes, ShouldEqual, 1) So(mock.dialCalledTimes, ShouldEqual, 1)
So(mock.usersCalledTimes, ShouldEqual, 1) So(mock.usersCalledTimes, ShouldEqual, 1)

View File

@ -91,10 +91,10 @@ func (mock *MockMultiLDAP) Users(logins []string) (
// User test fn // User test fn
func (mock *MockMultiLDAP) User(login string) ( func (mock *MockMultiLDAP) User(login string) (
*models.ExternalUserInfo, error, *models.ExternalUserInfo, ldap.ServerConfig, error,
) { ) {
mock.UserCalledTimes = mock.UserCalledTimes + 1 mock.UserCalledTimes = mock.UserCalledTimes + 1
return nil, nil return nil, ldap.ServerConfig{}, nil
} }
func setup() *MockLDAP { func setup() *MockLDAP {

View File

@ -27,6 +27,11 @@ func SearchOrgs(query *m.SearchOrgsQuery) error {
if query.Name != "" { if query.Name != "" {
sess.Where("name=?", query.Name) sess.Where("name=?", query.Name)
} }
if len(query.Ids) > 0 {
sess.In("id", query.Ids)
}
sess.Limit(query.Limit, query.Limit*query.Page) sess.Limit(query.Limit, query.Limit*query.Page)
sess.Cols("id", "name") sess.Cols("id", "name")
err := sess.Find(&query.Result) err := sess.Find(&query.Result)

View File

@ -2,19 +2,39 @@ package sqlstore
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"time" "time"
. "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
) )
func TestAccountDataAccess(t *testing.T) { func TestAccountDataAccess(t *testing.T) {
Convey("Testing Account DB Access", t, func() { Convey("Testing Account DB Access", t, func() {
InitTestDB(t) InitTestDB(t)
Convey("Given we have organizations, we can query them by IDs", func() {
var err error
var cmd *m.CreateOrgCommand
ids := []int64{}
for i := 1; i < 4; i++ {
cmd = &m.CreateOrgCommand{Name: fmt.Sprint("Org #", i)}
err = CreateOrg(cmd)
So(err, ShouldBeNil)
ids = append(ids, cmd.Result.Id)
}
query := &m.SearchOrgsQuery{Ids: ids}
err = SearchOrgs(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 3)
})
Convey("Given single org mode", func() { Convey("Given single org mode", func() {
setting.AutoAssignOrg = true setting.AutoAssignOrg = true
setting.AutoAssignOrgId = 1 setting.AutoAssignOrgId = 1