mirror of
https://github.com/grafana/grafana.git
synced 2025-08-06 01:19:57 +08:00
LDAP: Move to single package cluster (#63035)
* move multildap to ldap package * move LDAP api and tests to ldap package * register background service * lint
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -523,7 +523,6 @@ lerna.json @grafana/frontend-ops
|
||||
/pkg/services/guardian/ @grafana/grafana-authnz-team
|
||||
/pkg/services/ldap/ @grafana/grafana-authnz-team
|
||||
/pkg/services/login/ @grafana/grafana-authnz-team
|
||||
/pkg/services/multildap/ @grafana/grafana-authnz-team
|
||||
/pkg/services/oauthtoken/ @grafana/grafana-authnz-team
|
||||
/pkg/services/teamguardian/ @grafana/grafana-authnz-team
|
||||
/pkg/services/serviceaccounts/ @grafana/grafana-authnz-team
|
||||
|
@ -655,11 +655,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
adminRoute.Post("/provisioning/datasources/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersDatasources)), routing.Wrap(hs.AdminProvisioningReloadDatasources))
|
||||
adminRoute.Post("/provisioning/notifications/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersNotifications)), routing.Wrap(hs.AdminProvisioningReloadNotifications))
|
||||
adminRoute.Post("/provisioning/alerting/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersAlertRules)), routing.Wrap(hs.AdminProvisioningReloadAlerting))
|
||||
|
||||
adminRoute.Post("/ldap/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPConfigReload)), routing.Wrap(hs.ReloadLDAPCfg))
|
||||
adminRoute.Post("/ldap/sync/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPUsersSync)), routing.Wrap(hs.PostSyncUserWithLDAP))
|
||||
adminRoute.Get("/ldap/:username", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPUsersRead)), routing.Wrap(hs.GetUserFromLDAP))
|
||||
adminRoute.Get("/ldap/status", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)), routing.Wrap(hs.GetLDAPStatus))
|
||||
}, reqSignedIn)
|
||||
|
||||
// Administering users
|
||||
|
@ -56,7 +56,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/hooks"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
@ -174,7 +173,6 @@ type HTTPServer struct {
|
||||
grafanaUpdateChecker *updatechecker.GrafanaService
|
||||
pluginsUpdateChecker *updatechecker.PluginsService
|
||||
searchUsersService searchusers.Service
|
||||
ldapGroups ldap.Groups
|
||||
teamGuardian teamguardian.TeamGuardian
|
||||
queryDataService *query.Service
|
||||
serviceAccountsService serviceaccounts.Service
|
||||
@ -241,7 +239,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
encryptionService encryption.Internal, grafanaUpdateChecker *updatechecker.GrafanaService,
|
||||
pluginsUpdateChecker *updatechecker.PluginsService, searchUsersService searchusers.Service,
|
||||
dataSourcesService datasources.DataSourceService, queryDataService *query.Service,
|
||||
ldapGroups ldap.Groups, teamGuardian teamguardian.TeamGuardian, serviceaccountsService serviceaccounts.Service,
|
||||
teamGuardian teamguardian.TeamGuardian, serviceaccountsService serviceaccounts.Service,
|
||||
authInfoService login.AuthInfoService, storageService store.StorageService, httpEntityStore httpentitystore.HTTPEntityStore,
|
||||
notificationService *notifications.NotificationService, dashboardService dashboards.DashboardService,
|
||||
dashboardProvisioningService dashboards.DashboardProvisioningService, folderService folder.Service,
|
||||
@ -325,7 +323,6 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
httpEntityStore: httpEntityStore,
|
||||
DataSourcesService: dataSourcesService,
|
||||
searchUsersService: searchUsersService,
|
||||
ldapGroups: ldapGroups,
|
||||
teamGuardian: teamGuardian,
|
||||
queryDataService: queryDataService,
|
||||
serviceAccountsService: serviceaccountsService,
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
|
@ -9,9 +9,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
|
||||
"github.com/grafana/grafana/pkg/services/grpcserver"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
ldapapi "github.com/grafana/grafana/pkg/services/ldap/api"
|
||||
"github.com/grafana/grafana/pkg/services/live"
|
||||
"github.com/grafana/grafana/pkg/services/live/pushhttp"
|
||||
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
||||
@ -53,7 +54,7 @@ func ProvideBackgroundServiceRegistry(
|
||||
_ dashboardsnapshots.Service, _ *alerting.AlertNotificationService,
|
||||
_ serviceaccounts.Service, _ *guardian.Provider,
|
||||
_ *plugindashboardsservice.DashboardUpdater, _ *sanitizer.Provider,
|
||||
_ *grpcserver.HealthService, _ entity.EntityStoreServer, _ *grpcserver.ReflectionService,
|
||||
_ *grpcserver.HealthService, _ entity.EntityStoreServer, _ *grpcserver.ReflectionService, _ *ldapapi.Service,
|
||||
) *BackgroundServiceRegistry {
|
||||
return NewBackgroundServiceRegistry(
|
||||
httpServer,
|
||||
|
@ -68,6 +68,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/hooks"
|
||||
ldapapi "github.com/grafana/grafana/pkg/services/ldap/api"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||
"github.com/grafana/grafana/pkg/services/live"
|
||||
@ -248,6 +249,7 @@ var wireBasicSet = wire.NewSet(
|
||||
tracing.ProvideService,
|
||||
metrics.ProvideService,
|
||||
testdatasource.ProvideService,
|
||||
ldapapi.ProvideService,
|
||||
opentsdb.ProvideService,
|
||||
social.ProvideService,
|
||||
influxdb.ProvideService,
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
@ -18,8 +18,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
@ -13,8 +13,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
|
@ -9,23 +9,64 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
cfg *setting.Cfg
|
||||
userService user.Service
|
||||
authInfoService login.AuthInfoService
|
||||
ldapGroupsService ldap.Groups
|
||||
loginService login.Service
|
||||
orgService org.Service
|
||||
sessionService auth.UserTokenService
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, router routing.RouteRegister, accessControl ac.AccessControl,
|
||||
userService user.Service, authInfoService login.AuthInfoService, ldapGroupsService ldap.Groups,
|
||||
loginService login.Service, orgService org.Service, sessionService auth.UserTokenService) *Service {
|
||||
s := &Service{
|
||||
cfg: cfg,
|
||||
userService: userService,
|
||||
authInfoService: authInfoService,
|
||||
ldapGroupsService: ldapGroupsService,
|
||||
loginService: loginService,
|
||||
orgService: orgService,
|
||||
sessionService: sessionService,
|
||||
log: log.New("ldap.api"),
|
||||
}
|
||||
|
||||
authorize := ac.Middleware(accessControl)
|
||||
reqGrafanaAdmin := middleware.ReqGrafanaAdmin
|
||||
|
||||
router.Group("/api/admin", func(adminRoute routing.RouteRegister) {
|
||||
adminRoute.Post("/ldap/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPConfigReload)), routing.Wrap(s.ReloadLDAPCfg))
|
||||
adminRoute.Post("/ldap/sync/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPUsersSync)), routing.Wrap(s.PostSyncUserWithLDAP))
|
||||
adminRoute.Get("/ldap/:username", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPUsersRead)), routing.Wrap(s.GetUserFromLDAP))
|
||||
adminRoute.Get("/ldap/status", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)), routing.Wrap(s.GetLDAPStatus))
|
||||
}, middleware.ReqSignedIn)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
getLDAPConfig = multildap.GetConfig
|
||||
newLDAP = multildap.New
|
||||
|
||||
ldapLogger = log.New("LDAP.debug")
|
||||
|
||||
errOrganizationNotFound = func(orgId int64) error {
|
||||
return fmt.Errorf("unable to find organization with ID '%d'", orgId)
|
||||
}
|
||||
@ -117,7 +158,7 @@ func (user *LDAPUserDTO) FetchOrgs(ctx context.Context, orga org.Service) error
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) ReloadLDAPCfg(c *contextmodel.ReqContext) response.Response {
|
||||
func (s *Service) ReloadLDAPCfg(c *contextmodel.ReqContext) response.Response {
|
||||
if !ldap.IsEnabled() {
|
||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||
}
|
||||
@ -143,12 +184,12 @@ func (hs *HTTPServer) ReloadLDAPCfg(c *contextmodel.ReqContext) response.Respons
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) GetLDAPStatus(c *contextmodel.ReqContext) response.Response {
|
||||
func (s *Service) GetLDAPStatus(c *contextmodel.ReqContext) response.Response {
|
||||
if !ldap.IsEnabled() {
|
||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||
}
|
||||
|
||||
ldapConfig, err := getLDAPConfig(hs.Cfg)
|
||||
ldapConfig, err := getLDAPConfig(s.cfg)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
|
||||
}
|
||||
@ -196,12 +237,12 @@ func (hs *HTTPServer) GetLDAPStatus(c *contextmodel.ReqContext) response.Respons
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.Response {
|
||||
func (s *Service) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.Response {
|
||||
if !ldap.IsEnabled() {
|
||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||
}
|
||||
|
||||
ldapConfig, err := getLDAPConfig(hs.Cfg)
|
||||
ldapConfig, err := getLDAPConfig(s.cfg)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
|
||||
}
|
||||
@ -213,7 +254,7 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.
|
||||
|
||||
query := user.GetUserByIDQuery{ID: userId}
|
||||
|
||||
usr, err := hs.userService.GetByID(c.Req.Context(), &query)
|
||||
usr, err := s.userService.GetByID(c.Req.Context(), &query)
|
||||
if err != nil { // validate the userId exists
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return response.Error(404, user.ErrUserNotFound.Error(), nil)
|
||||
@ -223,7 +264,7 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.
|
||||
}
|
||||
|
||||
authModuleQuery := &login.GetAuthInfoQuery{UserId: usr.ID, AuthModule: login.LDAPAuthModule}
|
||||
if err := hs.authInfoService.GetAuthInfo(c.Req.Context(), authModuleQuery); err != nil { // validate the userId comes from LDAP
|
||||
if err := s.authInfoService.GetAuthInfo(c.Req.Context(), authModuleQuery); err != nil { // validate the userId comes from LDAP
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return response.Error(404, user.ErrUserNotFound.Error(), nil)
|
||||
}
|
||||
@ -235,19 +276,19 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.
|
||||
userInfo, _, err := ldapServer.User(usr.Login)
|
||||
if err != nil {
|
||||
if errors.Is(err, multildap.ErrDidNotFindUser) { // User was not in the LDAP server - we need to take action:
|
||||
if hs.Cfg.AdminUser == usr.Login { // User is *the* Grafana Admin. We cannot disable it.
|
||||
if s.cfg.AdminUser == usr.Login { // User is *the* Grafana Admin. We cannot disable it.
|
||||
errMsg := fmt.Sprintf(`Refusing to sync grafana super admin "%s" - it would be disabled`, usr.Login)
|
||||
ldapLogger.Error(errMsg)
|
||||
s.log.Error(errMsg)
|
||||
return response.Error(http.StatusBadRequest, errMsg, err)
|
||||
}
|
||||
|
||||
// Since the user was not in the LDAP server. Let's disable it.
|
||||
err := hs.Login.DisableExternalUser(c.Req.Context(), usr.Login)
|
||||
err := s.loginService.DisableExternalUser(c.Req.Context(), usr.Login)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to disable the user", err)
|
||||
}
|
||||
|
||||
err = hs.AuthTokenService.RevokeAllUserTokens(c.Req.Context(), userId)
|
||||
err = s.sessionService.RevokeAllUserTokens(c.Req.Context(), userId)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to remove session tokens for the user", err)
|
||||
}
|
||||
@ -255,14 +296,14 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.
|
||||
return response.Error(http.StatusBadRequest, "User not found in LDAP. Disabled the user without updating information", nil) // should this be a success?
|
||||
}
|
||||
|
||||
ldapLogger.Debug("Failed to sync the user with LDAP", "err", err)
|
||||
s.log.Debug("Failed to sync the user with LDAP", "err", err)
|
||||
return response.Error(http.StatusBadRequest, "Something went wrong while finding the user in LDAP", err)
|
||||
}
|
||||
|
||||
upsertCmd := &login.UpsertUserCommand{
|
||||
ReqContext: c,
|
||||
ExternalUser: userInfo,
|
||||
SignupAllowed: hs.Cfg.LDAPAllowSignup,
|
||||
SignupAllowed: s.cfg.LDAPAllowSignup,
|
||||
UserLookupParams: login.UserLookupParams{
|
||||
UserID: &usr.ID, // Upsert by ID only
|
||||
Email: nil,
|
||||
@ -270,7 +311,7 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.
|
||||
},
|
||||
}
|
||||
|
||||
err = hs.Login.UpsertUser(c.Req.Context(), upsertCmd)
|
||||
err = s.loginService.UpsertUser(c.Req.Context(), upsertCmd)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to update the user", err)
|
||||
}
|
||||
@ -292,12 +333,12 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) GetUserFromLDAP(c *contextmodel.ReqContext) response.Response {
|
||||
func (s *Service) GetUserFromLDAP(c *contextmodel.ReqContext) response.Response {
|
||||
if !ldap.IsEnabled() {
|
||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||
}
|
||||
|
||||
ldapConfig, err := getLDAPConfig(hs.Cfg)
|
||||
ldapConfig, err := getLDAPConfig(s.cfg)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration", err)
|
||||
}
|
||||
@ -315,7 +356,7 @@ func (hs *HTTPServer) GetUserFromLDAP(c *contextmodel.ReqContext) response.Respo
|
||||
return response.Error(http.StatusNotFound, "No user was found in the LDAP server(s) with that username", err)
|
||||
}
|
||||
|
||||
ldapLogger.Debug("user found", "user", user)
|
||||
s.log.Debug("user found", "user", user)
|
||||
|
||||
name, surname := splitName(user.Name)
|
||||
|
||||
@ -354,12 +395,12 @@ func (hs *HTTPServer) GetUserFromLDAP(c *contextmodel.ReqContext) response.Respo
|
||||
u.OrgRoles = append(u.OrgRoles, LDAPRoleDTO{GroupDN: userGroup})
|
||||
}
|
||||
|
||||
ldapLogger.Debug("mapping org roles", "orgsRoles", u.OrgRoles)
|
||||
if err := u.FetchOrgs(c.Req.Context(), hs.orgService); err != nil {
|
||||
s.log.Debug("mapping org roles", "orgsRoles", u.OrgRoles)
|
||||
if err := u.FetchOrgs(c.Req.Context(), s.orgService); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "An organization was not found - Please verify your LDAP configuration", err)
|
||||
}
|
||||
|
||||
u.Teams, err = hs.ldapGroups.GetTeams(user.Groups, orgIDs)
|
||||
u.Teams, err = s.ldapGroupsService.GetTeams(user.Groups, orgIDs)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Unable to find the teams for this user", err)
|
||||
}
|
@ -3,25 +3,22 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/db/dbtest"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -31,12 +28,12 @@ import (
|
||||
)
|
||||
|
||||
type LDAPMock struct {
|
||||
Results []*login.ExternalUserInfo
|
||||
Results []*login.ExternalUserInfo
|
||||
UserSearchResult *login.ExternalUserInfo
|
||||
UserSearchConfig ldap.ServerConfig
|
||||
UserSearchError error
|
||||
}
|
||||
|
||||
var userSearchResult *login.ExternalUserInfo
|
||||
var userSearchConfig ldap.ServerConfig
|
||||
var userSearchError error
|
||||
var pingResult []*multildap.ServerStatus
|
||||
var pingError error
|
||||
|
||||
@ -54,59 +51,76 @@ func (m *LDAPMock) Users(logins []string) ([]*login.ExternalUserInfo, error) {
|
||||
}
|
||||
|
||||
func (m *LDAPMock) User(login string) (*login.ExternalUserInfo, ldap.ServerConfig, error) {
|
||||
return userSearchResult, userSearchConfig, userSearchError
|
||||
return m.UserSearchResult, m.UserSearchConfig, m.UserSearchError
|
||||
}
|
||||
|
||||
// ***
|
||||
// GetUserFromLDAP tests
|
||||
// ***
|
||||
|
||||
func getUserFromLDAPContext(t *testing.T, requestURL string, searchOrgRst []*org.OrgDTO) *scenarioContext {
|
||||
func setupAPITest(t *testing.T, opts ...func(a *Service)) (*Service, *webtest.Server) {
|
||||
t.Helper()
|
||||
router := routing.NewRouteRegister()
|
||||
cfg := setting.NewCfg()
|
||||
|
||||
sc := setupScenarioContext(t, requestURL)
|
||||
a := ProvideService(cfg,
|
||||
router,
|
||||
acimpl.ProvideAccessControl(cfg),
|
||||
usertest.NewUserServiceFake(),
|
||||
&logintest.AuthInfoServiceFake{},
|
||||
ldap.ProvideGroupsService(),
|
||||
&logintest.LoginServiceFake{},
|
||||
&orgtest.FakeOrgService{},
|
||||
authtest.NewFakeUserAuthTokenService(),
|
||||
)
|
||||
|
||||
origLDAP := setting.LDAPEnabled
|
||||
setting.LDAPEnabled = true
|
||||
t.Cleanup(func() { setting.LDAPEnabled = origLDAP })
|
||||
for _, o := range opts {
|
||||
o(a)
|
||||
}
|
||||
|
||||
hs := &HTTPServer{Cfg: setting.NewCfg(), ldapGroups: ldap.ProvideGroupsService(), orgService: &orgtest.FakeOrgService{ExpectedOrgs: searchOrgRst}}
|
||||
server := webtest.NewServer(t, router)
|
||||
|
||||
sc.defaultHandler = routing.Wrap(func(c *contextmodel.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
|
||||
return a, server
|
||||
}
|
||||
|
||||
func TestGetUserFromLDAPAPIEndpoint_UserNotFound(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
}
|
||||
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{}
|
||||
return &LDAPMock{
|
||||
UserSearchResult: nil,
|
||||
}
|
||||
}
|
||||
|
||||
userSearchResult = nil
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.orgService = &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: []*org.OrgDTO{},
|
||||
}
|
||||
})
|
||||
|
||||
sc := getUserFromLDAPContext(t, "/api/admin/ldap/user-that-does-not-exist", []*org.OrgDTO{})
|
||||
req := server.NewGetRequest("/api/admin/ldap/user-that-does-not-exist")
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.user:read": {"*"}}},
|
||||
})
|
||||
|
||||
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())
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
assert.JSONEq(t, "{\"message\":\"No user was found in the LDAP server(s) with that username\"}", string(bodyBytes))
|
||||
}
|
||||
|
||||
func TestGetUserFromLDAPAPIEndpoint_OrgNotfound(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
|
||||
isAdmin := true
|
||||
userSearchResult = &login.ExternalUserInfo{
|
||||
userSearchResult := &login.ExternalUserInfo{
|
||||
Name: "John Doe",
|
||||
Email: "john.doe@example.com",
|
||||
Login: "johndoe",
|
||||
@ -115,7 +129,7 @@ func TestGetUserFromLDAPAPIEndpoint_OrgNotfound(t *testing.T) {
|
||||
IsGrafanaAdmin: &isAdmin,
|
||||
}
|
||||
|
||||
userSearchConfig = ldap.ServerConfig{
|
||||
userSearchConfig := ldap.ServerConfig{
|
||||
Attr: ldap.AttributeMap{
|
||||
Name: "ldap-name",
|
||||
Surname: "ldap-surname",
|
||||
@ -136,32 +150,54 @@ func TestGetUserFromLDAPAPIEndpoint_OrgNotfound(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{
|
||||
UserSearchResult: userSearchResult,
|
||||
UserSearchConfig: userSearchConfig,
|
||||
}
|
||||
}
|
||||
|
||||
mockOrgSearchResult := []*org.OrgDTO{
|
||||
{ID: 1, Name: "Main Org."},
|
||||
}
|
||||
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.orgService = &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: mockOrgSearchResult,
|
||||
}
|
||||
})
|
||||
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
}
|
||||
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{}
|
||||
}
|
||||
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.user:read": {"*"}}},
|
||||
})
|
||||
|
||||
sc := getUserFromLDAPContext(t, "/api/admin/ldap/johndoe", mockOrgSearchResult)
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, sc.resp.Code)
|
||||
assert.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
|
||||
var res map[string]interface{}
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &res)
|
||||
var resMap map[string]interface{}
|
||||
err = json.Unmarshal(bodyBytes, &resMap)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unable to find organization with ID '2'", res["error"])
|
||||
assert.Equal(t, "An organization was not found - Please verify your LDAP configuration", res["message"])
|
||||
assert.Equal(t, "unable to find organization with ID '2'", resMap["error"])
|
||||
assert.Equal(t, "An organization was not found - Please verify your LDAP configuration", resMap["message"])
|
||||
}
|
||||
|
||||
func TestGetUserFromLDAPAPIEndpoint(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
|
||||
isAdmin := true
|
||||
userSearchResult = &login.ExternalUserInfo{
|
||||
userSearchResult := &login.ExternalUserInfo{
|
||||
Name: "John Doe",
|
||||
Email: "john.doe@example.com",
|
||||
Login: "johndoe",
|
||||
@ -170,7 +206,7 @@ func TestGetUserFromLDAPAPIEndpoint(t *testing.T) {
|
||||
IsGrafanaAdmin: &isAdmin,
|
||||
}
|
||||
|
||||
userSearchConfig = ldap.ServerConfig{
|
||||
userSearchConfig := ldap.ServerConfig{
|
||||
Attr: ldap.AttributeMap{
|
||||
Name: "ldap-name",
|
||||
Surname: "ldap-surname",
|
||||
@ -200,12 +236,30 @@ func TestGetUserFromLDAPAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{}
|
||||
return &LDAPMock{
|
||||
UserSearchResult: userSearchResult,
|
||||
UserSearchConfig: userSearchConfig,
|
||||
}
|
||||
}
|
||||
|
||||
sc := getUserFromLDAPContext(t, "/api/admin/ldap/johndoe", mockOrgSearchResult)
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.orgService = &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: mockOrgSearchResult,
|
||||
}
|
||||
})
|
||||
|
||||
assert.Equal(t, sc.resp.Code, http.StatusOK)
|
||||
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.user:read": {"*"}}},
|
||||
})
|
||||
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
expected := `
|
||||
{
|
||||
@ -231,12 +285,16 @@ func TestGetUserFromLDAPAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
assert.JSONEq(t, expected, sc.resp.Body.String())
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
assert.JSONEq(t, expected, string(bodyBytes))
|
||||
}
|
||||
|
||||
func TestGetUserFromLDAPAPIEndpoint_WithTeamHandler(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
|
||||
isAdmin := true
|
||||
userSearchResult = &login.ExternalUserInfo{
|
||||
userSearchResult := &login.ExternalUserInfo{
|
||||
Name: "John Doe",
|
||||
Email: "john.doe@example.com",
|
||||
Login: "johndoe",
|
||||
@ -245,7 +303,7 @@ func TestGetUserFromLDAPAPIEndpoint_WithTeamHandler(t *testing.T) {
|
||||
IsGrafanaAdmin: &isAdmin,
|
||||
}
|
||||
|
||||
userSearchConfig = ldap.ServerConfig{
|
||||
userSearchConfig := ldap.ServerConfig{
|
||||
Attr: ldap.AttributeMap{
|
||||
Name: "ldap-name",
|
||||
Surname: "ldap-surname",
|
||||
@ -270,12 +328,30 @@ func TestGetUserFromLDAPAPIEndpoint_WithTeamHandler(t *testing.T) {
|
||||
}
|
||||
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{}
|
||||
return &LDAPMock{
|
||||
UserSearchResult: userSearchResult,
|
||||
UserSearchConfig: userSearchConfig,
|
||||
}
|
||||
}
|
||||
|
||||
sc := getUserFromLDAPContext(t, "/api/admin/ldap/johndoe", mockOrgSearchResult)
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.orgService = &orgtest.FakeOrgService{
|
||||
ExpectedOrgs: mockOrgSearchResult,
|
||||
}
|
||||
})
|
||||
|
||||
require.Equal(t, sc.resp.Code, http.StatusOK)
|
||||
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.user:read": {"*"}}},
|
||||
})
|
||||
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
expected := `
|
||||
{
|
||||
@ -300,41 +376,14 @@ func TestGetUserFromLDAPAPIEndpoint_WithTeamHandler(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
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 *contextmodel.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
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
assert.JSONEq(t, expected, string(bodyBytes))
|
||||
}
|
||||
|
||||
func TestGetLDAPStatusAPIEndpoint(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
|
||||
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},
|
||||
@ -349,9 +398,21 @@ func TestGetLDAPStatusAPIEndpoint(t *testing.T) {
|
||||
return &LDAPMock{}
|
||||
}
|
||||
|
||||
sc := getLDAPStatusContext(t)
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
req := server.NewGetRequest("/api/admin/ldap/status")
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.status:read": {}}},
|
||||
})
|
||||
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
expected := `
|
||||
[
|
||||
@ -360,70 +421,44 @@ func TestGetLDAPStatusAPIEndpoint(t *testing.T) {
|
||||
{ "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), userService user.Service) *scenarioContext {
|
||||
t.Helper()
|
||||
|
||||
sc := setupScenarioContext(t, requestURL)
|
||||
sc.authInfoService = &logintest.AuthInfoServiceFake{}
|
||||
|
||||
ldap := setting.LDAPEnabled
|
||||
t.Cleanup(func() {
|
||||
setting.LDAPEnabled = ldap
|
||||
})
|
||||
setting.LDAPEnabled = true
|
||||
|
||||
hs := &HTTPServer{
|
||||
Cfg: sc.cfg,
|
||||
AuthTokenService: authtest.NewFakeUserAuthTokenService(),
|
||||
Login: loginservice.LoginServiceMock{},
|
||||
authInfoService: sc.authInfoService,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc.defaultHandler = routing.Wrap(func(c *contextmodel.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
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
assert.JSONEq(t, expected, string(bodyBytes))
|
||||
}
|
||||
|
||||
func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
|
||||
userServiceMock := usertest.NewUserServiceFake()
|
||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||
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{}
|
||||
}
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
}
|
||||
|
||||
userSearchResult = &login.ExternalUserInfo{
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{UserSearchResult: &login.ExternalUserInfo{
|
||||
Login: "ldap-daniel",
|
||||
}
|
||||
}, userServiceMock)
|
||||
}}
|
||||
}
|
||||
|
||||
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.userService = userServiceMock
|
||||
})
|
||||
|
||||
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.user:sync": {}}},
|
||||
})
|
||||
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
expected := `
|
||||
{
|
||||
@ -431,23 +466,40 @@ func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
assert.JSONEq(t, expected, sc.resp.Body.String())
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
assert.JSONEq(t, expected, string(bodyBytes))
|
||||
}
|
||||
|
||||
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
userServiceMock := usertest.NewUserServiceFake()
|
||||
userServiceMock.ExpectedError = user.ErrUserNotFound
|
||||
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{}
|
||||
}
|
||||
}, userServiceMock)
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
}
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, sc.resp.Code)
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{}
|
||||
}
|
||||
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.userService = userServiceMock
|
||||
})
|
||||
|
||||
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.user:sync": {}}},
|
||||
})
|
||||
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||
|
||||
expected := `
|
||||
{
|
||||
@ -455,52 +507,83 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
assert.JSONEq(t, expected, sc.resp.Body.String())
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
assert.JSONEq(t, expected, string(bodyBytes))
|
||||
}
|
||||
|
||||
func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
|
||||
userServiceMock := usertest.NewUserServiceFake()
|
||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||
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{}
|
||||
}
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
}
|
||||
|
||||
userSearchError = multildap.ErrDidNotFindUser
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{UserSearchError: multildap.ErrDidNotFindUser}
|
||||
}
|
||||
|
||||
sc.cfg.AdminUser = "ldap-daniel"
|
||||
}, userServiceMock)
|
||||
assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.userService = userServiceMock
|
||||
a.cfg.AdminUser = "ldap-daniel"
|
||||
})
|
||||
|
||||
var res map[string]interface{}
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &res)
|
||||
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.user:sync": {}}},
|
||||
})
|
||||
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
var resMap map[string]interface{}
|
||||
err = json.Unmarshal(bodyBytes, &resMap)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "did not find a user", res["error"])
|
||||
assert.Equal(t, "Refusing to sync grafana super admin \"ldap-daniel\" - it would be disabled", res["message"])
|
||||
assert.Equal(t, "did not find a user", resMap["error"])
|
||||
assert.Equal(t, "Refusing to sync grafana super admin \"ldap-daniel\" - it would be disabled", resMap["message"])
|
||||
}
|
||||
|
||||
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
defer func() { setting.LDAPEnabled = false }()
|
||||
|
||||
userServiceMock := usertest.NewUserServiceFake()
|
||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.authInfoService.ExpectedExternalUser = &login.ExternalUserInfo{IsDisabled: true, UserId: 34}
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
}
|
||||
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{}
|
||||
}
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
}
|
||||
|
||||
userSearchResult = nil
|
||||
userSearchError = multildap.ErrDidNotFindUser
|
||||
}, userServiceMock)
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{UserSearchError: multildap.ErrDidNotFindUser}
|
||||
}
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.userService = userServiceMock
|
||||
a.authInfoService = &logintest.AuthInfoServiceFake{ExpectedExternalUser: &login.ExternalUserInfo{IsDisabled: true, UserId: 34}}
|
||||
})
|
||||
|
||||
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {"ldap.user:sync": {}}},
|
||||
})
|
||||
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
|
||||
expected := `
|
||||
{
|
||||
@ -508,14 +591,46 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
assert.JSONEq(t, expected, sc.resp.Body.String())
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
assert.JSONEq(t, expected, string(bodyBytes))
|
||||
}
|
||||
|
||||
// ***
|
||||
// Access control tests for ldap endpoints
|
||||
// ***
|
||||
|
||||
func TestLDAP_AccessControl(t *testing.T) {
|
||||
setting.LDAPEnabled = true
|
||||
|
||||
f, errC := os.CreateTemp("", "ldap.toml")
|
||||
require.NoError(t, errC)
|
||||
|
||||
_, errF := f.WriteString(
|
||||
`[[servers]]
|
||||
host = "127.0.0.1"
|
||||
port = 389
|
||||
search_filter = "(cn=%s)"
|
||||
search_base_dns = ["dc=grafana,dc=org"]`)
|
||||
require.NoError(t, errF)
|
||||
|
||||
setting.LDAPConfigFile = f.Name()
|
||||
|
||||
errF = f.Close()
|
||||
require.NoError(t, errF)
|
||||
|
||||
defer func() {
|
||||
setting.LDAPEnabled = false
|
||||
setting.LDAPConfigFile = ""
|
||||
}()
|
||||
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
}
|
||||
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{
|
||||
UserSearchResult: &login.ExternalUserInfo{
|
||||
Login: "ldap-daniel",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
desc string
|
||||
method string
|
||||
@ -600,41 +715,22 @@ func TestLDAP_AccessControl(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.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
|
||||
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPEnabled = true
|
||||
hs.Cfg = cfg
|
||||
hs.SQLStore = dbtest.NewFakeDB()
|
||||
hs.orgService = orgtest.NewOrgServiceFake()
|
||||
hs.userService = &usertest.FakeUserService{ExpectedUser: &user.User{}}
|
||||
hs.ldapGroups = &ldap.OSSGroups{}
|
||||
hs.Login = &loginservice.LoginServiceMock{}
|
||||
hs.authInfoService = &logintest.AuthInfoServiceFake{}
|
||||
_, server := setupAPITest(t, func(a *Service) {
|
||||
a.userService = &usertest.FakeUserService{ExpectedUser: &user.User{Login: "ldap-daniel", ID: 1}}
|
||||
})
|
||||
// Add minimal setup to pass handler
|
||||
userSearchResult = &login.ExternalUserInfo{}
|
||||
userSearchError = nil
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{}
|
||||
}
|
||||
|
||||
res, err := server.Send(webtest.RequestWithSignedInUser(server.NewRequest(tt.method, tt.url, nil), userWithPermissions(1, tt.permissions)))
|
||||
res, err := server.Send(
|
||||
webtest.RequestWithSignedInUser(server.NewRequest(tt.method, tt.url, nil),
|
||||
userWithPermissions(1, tt.permissions)))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedCode, res.StatusCode)
|
||||
|
||||
bodyBytes, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, tt.expectedCode, res.StatusCode, string(bodyBytes))
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func userWithPermissions(orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
|
||||
return &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
|
||||
}
|
Reference in New Issue
Block a user