mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			704 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			704 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
| package api
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"net/http"
 | |
| 	"net/http/httptest"
 | |
| 	"path/filepath"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/services/accesscontrol"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/api/response"
 | |
| 	"github.com/grafana/grafana/pkg/api/routing"
 | |
| 	"github.com/grafana/grafana/pkg/bus"
 | |
| 	"github.com/grafana/grafana/pkg/models"
 | |
| 	"github.com/grafana/grafana/pkg/services/auth"
 | |
| 	"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
 | |
| 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
 | |
| }
 | |
| 
 | |
| // ***
 | |
| // GetUserFromLDAP tests
 | |
| // ***
 | |
| 
 | |
| func getUserFromLDAPContext(t *testing.T, requestURL string) *scenarioContext {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	sc := setupScenarioContext(t, requestURL)
 | |
| 
 | |
| 	origLDAP := setting.LDAPEnabled
 | |
| 	setting.LDAPEnabled = true
 | |
| 	t.Cleanup(func() { setting.LDAPEnabled = origLDAP })
 | |
| 
 | |
| 	hs := &HTTPServer{Cfg: setting.NewCfg(), ldapGroups: ldap.ProvideGroupsService()}
 | |
| 
 | |
| 	sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.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(*setting.Cfg) (*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=grafana,dc=org",
 | |
| 				OrgId:   2,
 | |
| 				OrgRole: models.ROLE_VIEWER,
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	mockOrgSearchResult := []*models.OrgDTO{
 | |
| 		{Id: 1, Name: "Main Org."},
 | |
| 	}
 | |
| 
 | |
| 	bus.AddHandler("test", func(ctx context.Context, query *models.SearchOrgsQuery) error {
 | |
| 		query.Result = mockOrgSearchResult
 | |
| 		return nil
 | |
| 	})
 | |
| 
 | |
| 	getLDAPConfig = func(*setting.Cfg) (*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, http.StatusBadRequest, sc.resp.Code)
 | |
| 
 | |
| 	expected := `
 | |
| 	{
 | |
| 		"error": "unable to find organization with ID '2'",
 | |
| 		"message": "An organization 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,
 | |
| 			},
 | |
| 			{
 | |
| 				GroupDN: "cn=admins2,ou=groups,dc=grafana,dc=org",
 | |
| 				OrgId:   1,
 | |
| 				OrgRole: models.ROLE_ADMIN,
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	mockOrgSearchResult := []*models.OrgDTO{
 | |
| 		{Id: 1, Name: "Main Org."},
 | |
| 	}
 | |
| 
 | |
| 	bus.AddHandler("test", func(ctx context.Context, query *models.SearchOrgsQuery) error {
 | |
| 		query.Result = mockOrgSearchResult
 | |
| 		return nil
 | |
| 	})
 | |
| 
 | |
| 	getLDAPConfig = func(*setting.Cfg) (*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",
 | |
| 		Groups:         []string{"cn=admins,ou=groups,dc=grafana,dc=org"},
 | |
| 		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(ctx context.Context, query *models.SearchOrgsQuery) error {
 | |
| 		query.Result = mockOrgSearchResult
 | |
| 		return nil
 | |
| 	})
 | |
| 
 | |
| 	getLDAPConfig = func(*setting.Cfg) (*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": null
 | |
| 		}
 | |
| 	`
 | |
| 
 | |
| 	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(t, requestURL)
 | |
| 
 | |
| 	ldap := setting.LDAPEnabled
 | |
| 	setting.LDAPEnabled = true
 | |
| 	t.Cleanup(func() { setting.LDAPEnabled = ldap })
 | |
| 
 | |
| 	hs := &HTTPServer{Cfg: setting.NewCfg()}
 | |
| 
 | |
| 	sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.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(*setting.Cfg) (*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, preHook func(*testing.T, *scenarioContext)) *scenarioContext {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	sc := setupScenarioContext(t, requestURL)
 | |
| 
 | |
| 	ldap := setting.LDAPEnabled
 | |
| 	t.Cleanup(func() {
 | |
| 		setting.LDAPEnabled = ldap
 | |
| 	})
 | |
| 	setting.LDAPEnabled = true
 | |
| 
 | |
| 	hs := &HTTPServer{
 | |
| 		Cfg:              sc.cfg,
 | |
| 		AuthTokenService: auth.NewFakeUserAuthTokenService(),
 | |
| 	}
 | |
| 
 | |
| 	sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
 | |
| 		sc.context = c
 | |
| 		return hs.PostSyncUserWithLDAP(c)
 | |
| 	})
 | |
| 
 | |
| 	sc.m.Post("/api/admin/ldap/sync/:id", sc.defaultHandler)
 | |
| 
 | |
| 	sc.resp = httptest.NewRecorder()
 | |
| 	req, err := http.NewRequest(http.MethodPost, requestURL, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	preHook(t, sc)
 | |
| 
 | |
| 	sc.req = req
 | |
| 	sc.exec()
 | |
| 
 | |
| 	return sc
 | |
| }
 | |
| 
 | |
| func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
 | |
| 	sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
 | |
| 		getLDAPConfig = func(*setting.Cfg) (*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(ctx context.Context, cmd *models.UpsertUserCommand) error {
 | |
| 			require.Equal(t, "ldap-daniel", cmd.ExternalUser.Login)
 | |
| 			return nil
 | |
| 		})
 | |
| 
 | |
| 		bus.AddHandler("test", func(ctx context.Context, 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(ctx context.Context, q *models.GetAuthInfoQuery) error {
 | |
| 			require.Equal(t, q.UserId, int64(34))
 | |
| 			require.Equal(t, q.AuthModule, models.AuthModuleLDAP)
 | |
| 
 | |
| 			return nil
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	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) {
 | |
| 	sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
 | |
| 		getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
 | |
| 			return &ldap.Config{}, nil
 | |
| 		}
 | |
| 
 | |
| 		newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
 | |
| 			return &LDAPMock{}
 | |
| 		}
 | |
| 
 | |
| 		bus.AddHandler("test", func(ctx context.Context, q *models.GetUserByIdQuery) error {
 | |
| 			require.Equal(t, q.Id, int64(34))
 | |
| 
 | |
| 			return models.ErrUserNotFound
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	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) {
 | |
| 	sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
 | |
| 		getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
 | |
| 			return &ldap.Config{}, nil
 | |
| 		}
 | |
| 
 | |
| 		newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
 | |
| 			return &LDAPMock{}
 | |
| 		}
 | |
| 
 | |
| 		userSearchError = multildap.ErrDidNotFindUser
 | |
| 
 | |
| 		sc.cfg.AdminUser = "ldap-daniel"
 | |
| 
 | |
| 		bus.AddHandler("test", func(ctx context.Context, 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(ctx context.Context, q *models.GetAuthInfoQuery) error {
 | |
| 			require.Equal(t, q.UserId, int64(34))
 | |
| 			require.Equal(t, q.AuthModule, models.AuthModuleLDAP)
 | |
| 
 | |
| 			return nil
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
 | |
| 
 | |
| 	expected := `
 | |
| 	{
 | |
| 		"error": "did not find a user",
 | |
| 		"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) {
 | |
| 	sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
 | |
| 		getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
 | |
| 			return &ldap.Config{}, nil
 | |
| 		}
 | |
| 
 | |
| 		newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
 | |
| 			return &LDAPMock{}
 | |
| 		}
 | |
| 
 | |
| 		userSearchResult = nil
 | |
| 
 | |
| 		bus.AddHandler("test", func(ctx context.Context, cmd *models.UpsertUserCommand) error {
 | |
| 			require.Equal(t, "ldap-daniel", cmd.ExternalUser.Login)
 | |
| 			return nil
 | |
| 		})
 | |
| 
 | |
| 		bus.AddHandler("test", func(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, cmd *models.DisableUserCommand) error {
 | |
| 			assert.Equal(t, 34, cmd.UserId)
 | |
| 			return nil
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
 | |
| 
 | |
| 	expected := `
 | |
| 	{
 | |
| 		"message": "User not found in LDAP. Disabled the user without updating information"
 | |
| 	}
 | |
| 	`
 | |
| 
 | |
| 	assert.JSONEq(t, expected, sc.resp.Body.String())
 | |
| }
 | |
| 
 | |
| // ***
 | |
| // Access control tests for ldap endpoints
 | |
| // ***
 | |
| 
 | |
| func TestLDAP_AccessControl(t *testing.T) {
 | |
| 	tests := []accessControlTestCase{
 | |
| 		{
 | |
| 			url:          "/api/admin/ldap/reload",
 | |
| 			method:       http.MethodPost,
 | |
| 			desc:         "ReloadLDAPCfg should return 200 for user with correct permissions",
 | |
| 			expectedCode: http.StatusOK,
 | |
| 			permissions: []*accesscontrol.Permission{
 | |
| 				{Action: accesscontrol.ActionLDAPConfigReload},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			url:          "/api/admin/ldap/reload",
 | |
| 			method:       http.MethodPost,
 | |
| 			desc:         "ReloadLDAPCfg should return 403 for user without required permissions",
 | |
| 			expectedCode: http.StatusForbidden,
 | |
| 			permissions: []*accesscontrol.Permission{
 | |
| 				{Action: "wrong"},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			url:          "/api/admin/ldap/status",
 | |
| 			method:       http.MethodGet,
 | |
| 			desc:         "GetLDAPStatus should return 200 for user without required permissions",
 | |
| 			expectedCode: http.StatusOK,
 | |
| 			permissions: []*accesscontrol.Permission{
 | |
| 				{Action: accesscontrol.ActionLDAPStatusRead},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			url:          "/api/admin/ldap/status",
 | |
| 			method:       http.MethodGet,
 | |
| 			desc:         "GetLDAPStatus should return 200 for user without required permissions",
 | |
| 			expectedCode: http.StatusForbidden,
 | |
| 			permissions: []*accesscontrol.Permission{
 | |
| 				{Action: "wrong"},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			url:          "/api/admin/ldap/test",
 | |
| 			method:       http.MethodGet,
 | |
| 			desc:         "GetUserFromLDAP should return 200 for user with required permissions",
 | |
| 			expectedCode: http.StatusOK,
 | |
| 			permissions: []*accesscontrol.Permission{
 | |
| 				{Action: accesscontrol.ActionLDAPUsersRead},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			url:          "/api/admin/ldap/test",
 | |
| 			method:       http.MethodGet,
 | |
| 			desc:         "GetUserFromLDAP should return 403 for user without required permissions",
 | |
| 			expectedCode: http.StatusForbidden,
 | |
| 			permissions: []*accesscontrol.Permission{
 | |
| 				{Action: "wrong"},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			url:          "/api/admin/ldap/sync/1",
 | |
| 			method:       http.MethodPost,
 | |
| 			desc:         "PostSyncUserWithLDAP should return 200 for user without required permissions",
 | |
| 			expectedCode: http.StatusOK,
 | |
| 			permissions: []*accesscontrol.Permission{
 | |
| 				{Action: accesscontrol.ActionLDAPUsersSync},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			url:          "/api/admin/ldap/sync/1",
 | |
| 			method:       http.MethodPost,
 | |
| 			desc:         "PostSyncUserWithLDAP should return 200 for user without required permissions",
 | |
| 			expectedCode: http.StatusForbidden,
 | |
| 			permissions: []*accesscontrol.Permission{
 | |
| 				{Action: "wrong"},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		t.Run(test.desc, func(t *testing.T) {
 | |
| 			enabled := setting.LDAPEnabled
 | |
| 			configFile := setting.LDAPConfigFile
 | |
| 
 | |
| 			t.Cleanup(func() {
 | |
| 				setting.LDAPEnabled = enabled
 | |
| 				setting.LDAPConfigFile = configFile
 | |
| 			})
 | |
| 
 | |
| 			setting.LDAPEnabled = true
 | |
| 			path, err := filepath.Abs("../../conf/ldap.toml")
 | |
| 			assert.NoError(t, err)
 | |
| 			setting.LDAPConfigFile = path
 | |
| 
 | |
| 			cfg := setting.NewCfg()
 | |
| 			cfg.LDAPEnabled = true
 | |
| 
 | |
| 			sc, _ := setupAccessControlScenarioContext(t, cfg, test.url, test.permissions)
 | |
| 			sc.resp = httptest.NewRecorder()
 | |
| 			sc.req, err = http.NewRequest(test.method, test.url, nil)
 | |
| 			assert.NoError(t, err)
 | |
| 
 | |
| 			// Add minimal setup to pass handler
 | |
| 			userSearchResult = &models.ExternalUserInfo{}
 | |
| 			userSearchError = nil
 | |
| 			newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
 | |
| 				return &LDAPMock{}
 | |
| 			}
 | |
| 
 | |
| 			bus.AddHandler("test", func(ctx context.Context, q *models.GetUserByIdQuery) error {
 | |
| 				q.Result = &models.User{}
 | |
| 				return nil
 | |
| 			})
 | |
| 
 | |
| 			bus.AddHandler("test", func(ctx context.Context, q *models.GetAuthInfoQuery) error {
 | |
| 				return nil
 | |
| 			})
 | |
| 
 | |
| 			bus.AddHandler("test", func(ctx context.Context, cmd *models.UpsertUserCommand) error {
 | |
| 				return nil
 | |
| 			})
 | |
| 
 | |
| 			sc.exec()
 | |
| 			assert.Equal(t, test.expectedCode, sc.resp.Code)
 | |
| 		})
 | |
| 	}
 | |
| }
 |