| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569 |
- package api
- import (
- "context"
- "errors"
- "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
- }
- type TokenServiceMock struct {
- }
- var userSearchResult *models.ExternalUserInfo
- var userSearchConfig ldap.ServerConfig
- var userSearchError error
- var pingResult []*multildap.ServerStatus
- var pingError error
- func (m *LDAPMock) Ping() ([]*multildap.ServerStatus, error) {
- return pingResult, pingError
- }
- 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, userSearchError
- }
- func (ts *TokenServiceMock) RevokeAllUserTokens(ctx context.Context, userId int64) error {
- return nil
- }
- //***
- // GetUserFromLDAP tests
- //***
- func getUserFromLDAPContext(t *testing.T, requestURL string) *scenarioContext {
- t.Helper()
- sc := setupScenarioContext(requestURL)
- ldap := setting.LDAPEnabled
- setting.LDAPEnabled = true
- defer func() { setting.LDAPEnabled = ldap }()
- 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)
- assert.JSONEq(t, "{\"message\":\"No user was found in the LDAP server(s) with that username\"}", sc.resp.Body.String())
- }
- func TestGetUserFromLDAPApiEndpoint_OrgNotfound(t *testing.T) {
- isAdmin := true
- userSearchResult = &models.ExternalUserInfo{
- Name: "John Doe",
- Email: "john.doe@example.com",
- Login: "johndoe",
- Groups: []string{"cn=admins,ou=groups,dc=grafana,dc=org"},
- 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)
- expected := `
- {
- "error": "Unable to find organization with ID '2'",
- "message": "An oganization was not found - Please verify your LDAP configuration"
- }
- `
- assert.JSONEq(t, expected, sc.resp.Body.String())
- }
- func TestGetUserFromLDAPApiEndpoint(t *testing.T) {
- isAdmin := true
- userSearchResult = &models.ExternalUserInfo{
- Name: "John Doe",
- Email: "john.doe@example.com",
- Login: "johndoe",
- Groups: []string{"cn=admins,ou=groups,dc=grafana,dc=org", "another-group-not-matched"},
- 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")
- assert.Equal(t, sc.resp.Code, http.StatusOK)
- 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" },
- { "orgId": 0, "orgRole": "", "orgName": "", "groupDN": "another-group-not-matched" }
- ],
- "teams": null
- }
- `
- assert.JSONEq(t, expected, sc.resp.Body.String())
- }
- func TestGetUserFromLDAPApiEndpoint_WithTeamHandler(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
- })
- bus.AddHandler("test", func(cmd *models.GetTeamsForLDAPGroupCommand) error {
- cmd.Result = []models.TeamOrgGroupDTO{}
- 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)
- 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": []
- }
- `
- assert.JSONEq(t, expected, sc.resp.Body.String())
- }
- //***
- // GetLDAPStatus tests
- //***
- func getLDAPStatusContext(t *testing.T) *scenarioContext {
- t.Helper()
- requestURL := "/api/admin/ldap/status"
- sc := setupScenarioContext(requestURL)
- ldap := setting.LDAPEnabled
- setting.LDAPEnabled = true
- defer func() { setting.LDAPEnabled = ldap }()
- hs := &HTTPServer{Cfg: setting.NewCfg()}
- sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
- sc.context = c
- return hs.GetLDAPStatus(c)
- })
- sc.m.Get("/api/admin/ldap/status", sc.defaultHandler)
- sc.resp = httptest.NewRecorder()
- req, _ := http.NewRequest(http.MethodGet, requestURL, nil)
- sc.req = req
- sc.exec()
- return sc
- }
- func TestGetLDAPStatusApiEndpoint(t *testing.T) {
- pingResult = []*multildap.ServerStatus{
- {Host: "10.0.0.3", Port: 361, Available: true, Error: nil},
- {Host: "10.0.0.3", Port: 362, Available: true, Error: nil},
- {Host: "10.0.0.5", Port: 361, Available: false, Error: errors.New("something is awfully wrong")},
- }
- getLDAPConfig = func() (*ldap.Config, error) {
- return &ldap.Config{}, nil
- }
- newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
- return &LDAPMock{}
- }
- sc := getLDAPStatusContext(t)
- require.Equal(t, http.StatusOK, sc.resp.Code)
- expected := `
- [
- { "host": "10.0.0.3", "port": 361, "available": true, "error": "" },
- { "host": "10.0.0.3", "port": 362, "available": true, "error": "" },
- { "host": "10.0.0.5", "port": 361, "available": false, "error": "something is awfully wrong" }
- ]
- `
- assert.JSONEq(t, expected, sc.resp.Body.String())
- }
- //***
- // PostSyncUserWithLDAP tests
- //***
- func postSyncUserWithLDAPContext(t *testing.T, requestURL string) *scenarioContext {
- t.Helper()
- sc := setupScenarioContext(requestURL)
- ldap := setting.LDAPEnabled
- setting.LDAPEnabled = true
- defer func() { setting.LDAPEnabled = ldap }()
- hs := &HTTPServer{Cfg: setting.NewCfg()}
- sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
- sc.context = c
- return hs.PostSyncUserWithLDAP(c)
- })
- sc.m.Post("/api/admin/ldap/sync/:id", sc.defaultHandler)
- sc.resp = httptest.NewRecorder()
- req, _ := http.NewRequest(http.MethodPost, requestURL, nil)
- sc.req = req
- sc.exec()
- return sc
- }
- func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
- getLDAPConfig = func() (*ldap.Config, error) {
- return &ldap.Config{}, nil
- }
- newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
- return &LDAPMock{}
- }
- userSearchResult = &models.ExternalUserInfo{
- Login: "ldap-daniel",
- }
- bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
- require.Equal(t, "ldap-daniel", cmd.ExternalUser.Login)
- return nil
- })
- bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
- require.Equal(t, q.Id, int64(34))
- q.Result = &models.User{Login: "ldap-daniel", Id: 34}
- return nil
- })
- bus.AddHandler("test", func(q *models.GetAuthInfoQuery) error {
- require.Equal(t, q.UserId, int64(34))
- require.Equal(t, q.AuthModule, models.AuthModuleLDAP)
- return nil
- })
- sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
- assert.Equal(t, http.StatusOK, sc.resp.Code)
- expected := `
- {
- "message": "User synced successfully"
- }
- `
- assert.JSONEq(t, expected, sc.resp.Body.String())
- }
- func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
- getLDAPConfig = func() (*ldap.Config, error) {
- return &ldap.Config{}, nil
- }
- newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
- return &LDAPMock{}
- }
- bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
- require.Equal(t, q.Id, int64(34))
- return models.ErrUserNotFound
- })
- sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
- assert.Equal(t, http.StatusNotFound, sc.resp.Code)
- expected := `
- {
- "message": "User not found"
- }
- `
- assert.JSONEq(t, expected, sc.resp.Body.String())
- }
- func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
- getLDAPConfig = func() (*ldap.Config, error) {
- return &ldap.Config{}, nil
- }
- newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
- return &LDAPMock{}
- }
- userSearchError = ldap.ErrCouldNotFindUser
- admin := setting.AdminUser
- setting.AdminUser = "ldap-daniel"
- defer func() { setting.AdminUser = admin }()
- bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
- require.Equal(t, q.Id, int64(34))
- q.Result = &models.User{Login: "ldap-daniel", Id: 34}
- return nil
- })
- bus.AddHandler("test", func(q *models.GetAuthInfoQuery) error {
- require.Equal(t, q.UserId, int64(34))
- require.Equal(t, q.AuthModule, models.AuthModuleLDAP)
- return nil
- })
- sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
- assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
- expected := `
- {
- "error": "Can't find user in LDAP",
- "message": "Refusing to sync grafana super admin \"ldap-daniel\" - it would be disabled"
- }
- `
- assert.JSONEq(t, expected, sc.resp.Body.String())
- }
- func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
- getLDAPConfig = func() (*ldap.Config, error) {
- return &ldap.Config{}, nil
- }
- tokenService = &TokenServiceMock{}
- newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
- return &LDAPMock{}
- }
- userSearchResult = nil
- bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
- require.Equal(t, "ldap-daniel", cmd.ExternalUser.Login)
- return nil
- })
- bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
- require.Equal(t, q.Id, int64(34))
- q.Result = &models.User{Login: "ldap-daniel", Id: 34}
- return nil
- })
- bus.AddHandler("test", func(q *models.GetExternalUserInfoByLoginQuery) error {
- assert.Equal(t, "ldap-daniel", q.LoginOrEmail)
- q.Result = &models.ExternalUserInfo{IsDisabled: true, UserId: 34}
- return nil
- })
- bus.AddHandler("test", func(cmd *models.DisableUserCommand) error {
- assert.Equal(t, 34, cmd.UserId)
- return nil
- })
- sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
- assert.Equal(t, http.StatusOK, sc.resp.Code)
- expected := `
- {
- "message": "User disabled without any updates in the information"
- }
- `
- assert.JSONEq(t, expected, sc.resp.Body.String())
- }
|