Teams: Move team API to own service (#76347)

* move team API to its own service

* remove uneeded import

* reshare pref api logic
This commit is contained in:
Jo 2023-10-12 10:10:54 +02:00 committed by GitHub
parent 665dc1fa44
commit 466f8a1f5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 343 additions and 223 deletions

View File

@ -270,25 +270,6 @@ func (hs *HTTPServer) registerRoutes() {
usersRoute.Post("/:id/using/:orgId", authorize(ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), routing.Wrap(hs.UpdateUserActiveOrg))
}, requestmeta.SetOwner(requestmeta.TeamAuth))
// team (admin permission required)
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
teamsRoute.Post("/", authorize(ac.EvalPermission(ac.ActionTeamsCreate)), routing.Wrap(hs.CreateTeam))
teamsRoute.Put("/:teamId", authorize(ac.EvalPermission(ac.ActionTeamsWrite, ac.ScopeTeamsID)), routing.Wrap(hs.UpdateTeam))
teamsRoute.Delete("/:teamId", authorize(ac.EvalPermission(ac.ActionTeamsDelete, ac.ScopeTeamsID)), routing.Wrap(hs.DeleteTeamByID))
teamsRoute.Get("/:teamId/members", authorize(ac.EvalPermission(ac.ActionTeamsPermissionsRead, ac.ScopeTeamsID)), routing.Wrap(hs.GetTeamMembers))
teamsRoute.Post("/:teamId/members", authorize(ac.EvalPermission(ac.ActionTeamsPermissionsWrite, ac.ScopeTeamsID)), routing.Wrap(hs.AddTeamMember))
teamsRoute.Put("/:teamId/members/:userId", authorize(ac.EvalPermission(ac.ActionTeamsPermissionsWrite, ac.ScopeTeamsID)), routing.Wrap(hs.UpdateTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", authorize(ac.EvalPermission(ac.ActionTeamsPermissionsWrite, ac.ScopeTeamsID)), routing.Wrap(hs.RemoveTeamMember))
teamsRoute.Get("/:teamId/preferences", authorize(ac.EvalPermission(ac.ActionTeamsRead, ac.ScopeTeamsID)), routing.Wrap(hs.GetTeamPreferences))
teamsRoute.Put("/:teamId/preferences", authorize(ac.EvalPermission(ac.ActionTeamsWrite, ac.ScopeTeamsID)), routing.Wrap(hs.UpdateTeamPreferences))
}, requestmeta.SetOwner(requestmeta.TeamAuth))
// team without requirement of user to be org admin
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
teamsRoute.Get("/:teamId", authorize(ac.EvalPermission(ac.ActionTeamsRead, ac.ScopeTeamsID)), routing.Wrap(hs.GetTeamByID))
teamsRoute.Get("/search", authorize(ac.EvalPermission(ac.ActionTeamsRead)), routing.Wrap(hs.SearchTeams))
}, requestmeta.SetOwner(requestmeta.TeamAuth))
// org information available to all users.
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
orgRoute.Get("/", authorize(ac.EvalPermission(ac.ActionOrgsRead)), routing.Wrap(hs.GetCurrentOrg))

View File

@ -214,7 +214,6 @@ func setupScenarioContext(t *testing.T, url string) *scenarioContext {
return sc
}
// FIXME: This user should not be anonymous
func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
}

View File

@ -170,7 +170,6 @@ type HTTPServer struct {
queryDataService query.Service
serviceAccountsService serviceaccounts.Service
authInfoService login.AuthInfoService
teamPermissionsService accesscontrol.TeamPermissionsService
NotificationService *notifications.NotificationService
DashboardService dashboards.DashboardService
dashboardProvisioningService dashboards.DashboardProvisioningService
@ -236,7 +235,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
dsGuardian guardian.DatasourceGuardianProvider, alertNotificationService *alerting.AlertNotificationService,
dashboardsnapshotsService dashboardsnapshots.Service, pluginSettings pluginSettings.Service,
avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service,
teamsPermissionsService accesscontrol.TeamPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service,
starService star.Service, csrfService csrf.Service, basekinds *corekind.Base,
playlistService playlist.Service, apiKeyService apikey.Service, kvStore kvstore.KVStore,
@ -319,7 +318,6 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
dashboardProvisioningService: dashboardProvisioningService,
folderService: folderService,
dsGuardian: dsGuardian,
teamPermissionsService: teamsPermissionsService,
AlertNotificationService: alertNotificationService,
dashboardsnapshotsService: dashboardsnapshotsService,
PluginSettings: pluginSettings,

View File

@ -11,6 +11,7 @@ import (
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/services/preference/prefapi"
"github.com/grafana/grafana/pkg/web"
)
@ -68,56 +69,7 @@ func (hs *HTTPServer) GetUserPreferences(c *contextmodel.ReqContext) response.Re
return response.Error(http.StatusInternalServerError, "Failed to get user preferences", errID)
}
return hs.getPreferencesFor(c.Req.Context(), c.SignedInUser.GetOrgID(), userID, 0)
}
func (hs *HTTPServer) getPreferencesFor(ctx context.Context, orgID, userID, teamID int64) response.Response {
prefsQuery := pref.GetPreferenceQuery{UserID: userID, OrgID: orgID, TeamID: teamID}
preference, err := hs.preferenceService.Get(ctx, &prefsQuery)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get preferences", err)
}
var dashboardUID string
// when homedashboardID is 0, that means it is the default home dashboard, no UID would be returned in the response
if preference.HomeDashboardID != 0 {
query := dashboards.GetDashboardQuery{ID: preference.HomeDashboardID, OrgID: orgID}
queryResult, err := hs.DashboardService.GetDashboard(ctx, &query)
if err == nil {
dashboardUID = queryResult.UID
}
}
dto := preferences.Spec{}
if preference.WeekStart != nil && *preference.WeekStart != "" {
dto.WeekStart = preference.WeekStart
}
if preference.Theme != "" {
dto.Theme = &preference.Theme
}
if dashboardUID != "" {
dto.HomeDashboardUID = &dashboardUID
}
if preference.Timezone != "" {
dto.Timezone = &preference.Timezone
}
if preference.JSONData != nil {
if preference.JSONData.Language != "" {
dto.Language = &preference.JSONData.Language
}
if preference.JSONData.QueryHistory.HomeTab != "" {
dto.QueryHistory = &preferences.QueryHistoryPreference{
HomeTab: &preference.JSONData.QueryHistory.HomeTab,
}
}
}
return response.JSON(http.StatusOK, &dto)
return prefapi.GetPreferencesFor(c.Req.Context(), hs.DashboardService, hs.preferenceService, c.SignedInUser.GetOrgID(), userID, 0)
}
// swagger:route PUT /user/preferences user_preferences updateUserPreferences
@ -142,48 +94,8 @@ func (hs *HTTPServer) UpdateUserPreferences(c *contextmodel.ReqContext) response
return response.Error(http.StatusInternalServerError, "Failed to update user preferences", errID)
}
return hs.updatePreferencesFor(c.Req.Context(), c.SignedInUser.GetOrgID(), userID, 0, &dtoCmd)
}
func (hs *HTTPServer) updatePreferencesFor(ctx context.Context, orgID, userID, teamId int64, dtoCmd *dtos.UpdatePrefsCmd) response.Response {
if dtoCmd.Theme != "" && !pref.IsValidThemeID(dtoCmd.Theme) {
return response.Error(http.StatusBadRequest, "Invalid theme", nil)
}
dashboardID := dtoCmd.HomeDashboardID
if dtoCmd.HomeDashboardUID != nil {
query := dashboards.GetDashboardQuery{UID: *dtoCmd.HomeDashboardUID, OrgID: orgID}
if query.UID == "" {
// clear the value
dashboardID = 0
} else {
queryResult, err := hs.DashboardService.GetDashboard(ctx, &query)
if err != nil {
return response.Error(http.StatusNotFound, "Dashboard not found", err)
}
dashboardID = queryResult.ID
}
}
dtoCmd.HomeDashboardID = dashboardID
saveCmd := pref.SavePreferenceCommand{
UserID: userID,
OrgID: orgID,
TeamID: teamId,
Theme: dtoCmd.Theme,
Language: dtoCmd.Language,
Timezone: dtoCmd.Timezone,
WeekStart: dtoCmd.WeekStart,
HomeDashboardID: dtoCmd.HomeDashboardID,
QueryHistory: dtoCmd.QueryHistory,
CookiePreferences: dtoCmd.Cookies,
}
if err := hs.preferenceService.Save(ctx, &saveCmd); err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to save preferences", err)
}
return response.Success("Preferences updated")
return prefapi.UpdatePreferencesFor(c.Req.Context(), hs.DashboardService,
hs.preferenceService, c.SignedInUser.GetOrgID(), userID, 0, &dtoCmd)
}
// swagger:route PATCH /user/preferences user_preferences patchUserPreferences
@ -262,7 +174,7 @@ func (hs *HTTPServer) patchPreferencesFor(ctx context.Context, orgID, userID, te
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) GetOrgPreferences(c *contextmodel.ReqContext) response.Response {
return hs.getPreferencesFor(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, 0)
return prefapi.GetPreferencesFor(c.Req.Context(), hs.DashboardService, hs.preferenceService, c.SignedInUser.GetOrgID(), 0, 0)
}
// swagger:route PUT /org/preferences org_preferences updateOrgPreferences
@ -281,7 +193,7 @@ func (hs *HTTPServer) UpdateOrgPreferences(c *contextmodel.ReqContext) response.
return response.Error(http.StatusBadRequest, "bad request data", err)
}
return hs.updatePreferencesFor(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, 0, &dtoCmd)
return prefapi.UpdatePreferencesFor(c.Req.Context(), hs.DashboardService, hs.preferenceService, c.SignedInUser.GetOrgID(), 0, 0, &dtoCmd)
}
// swagger:route PATCH /org/preferences org_preferences patchOrgPreferences

View File

@ -40,6 +40,7 @@ import (
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/store/sanitizer"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
"github.com/grafana/grafana/pkg/services/team/teamapi"
"github.com/grafana/grafana/pkg/services/updatechecker"
)
@ -62,7 +63,7 @@ func ProvideBackgroundServiceRegistry(
_ serviceaccounts.Service, _ *guardian.Provider,
_ *plugindashboardsservice.DashboardUpdater, _ *sanitizer.Provider,
_ *grpcserver.HealthService, _ entity.EntityStoreServer, _ *grpcserver.ReflectionService, _ *ldapapi.Service,
_ *apiregistry.Service, _ auth.IDService,
_ *apiregistry.Service, _ auth.IDService, _ *teamapi.TeamAPI,
) *BackgroundServiceRegistry {
return NewBackgroundServiceRegistry(
httpServer,

View File

@ -141,6 +141,7 @@ import (
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
"github.com/grafana/grafana/pkg/services/tag"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/team/teamapi"
"github.com/grafana/grafana/pkg/services/team/teamimpl"
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
"github.com/grafana/grafana/pkg/services/temp_user/tempuserimpl"
@ -342,6 +343,7 @@ var wireBasicSet = wire.NewSet(
resolver.ProvideEntityReferenceResolver,
httpentitystore.ProvideHTTPEntityStore,
teamimpl.ProvideService,
teamapi.ProvideTeamAPI,
tempuserimpl.ProvideService,
loginattemptimpl.ProvideService,
wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)),

View File

@ -0,0 +1,107 @@
// shared logic between httpserver and teamapi
package prefapi
import (
"context"
"net/http"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/kinds/preferences"
"github.com/grafana/grafana/pkg/services/dashboards"
pref "github.com/grafana/grafana/pkg/services/preference"
)
func UpdatePreferencesFor(ctx context.Context,
dashboardService dashboards.DashboardService, preferenceService pref.Service,
orgID, userID, teamId int64, dtoCmd *dtos.UpdatePrefsCmd) response.Response {
if dtoCmd.Theme != "" && !pref.IsValidThemeID(dtoCmd.Theme) {
return response.Error(http.StatusBadRequest, "Invalid theme", nil)
}
dashboardID := dtoCmd.HomeDashboardID
if dtoCmd.HomeDashboardUID != nil {
query := dashboards.GetDashboardQuery{UID: *dtoCmd.HomeDashboardUID, OrgID: orgID}
if query.UID == "" {
// clear the value
dashboardID = 0
} else {
queryResult, err := dashboardService.GetDashboard(ctx, &query)
if err != nil {
return response.Error(http.StatusNotFound, "Dashboard not found", err)
}
dashboardID = queryResult.ID
}
}
dtoCmd.HomeDashboardID = dashboardID
saveCmd := pref.SavePreferenceCommand{
UserID: userID,
OrgID: orgID,
TeamID: teamId,
Theme: dtoCmd.Theme,
Language: dtoCmd.Language,
Timezone: dtoCmd.Timezone,
WeekStart: dtoCmd.WeekStart,
HomeDashboardID: dtoCmd.HomeDashboardID,
QueryHistory: dtoCmd.QueryHistory,
CookiePreferences: dtoCmd.Cookies,
}
if err := preferenceService.Save(ctx, &saveCmd); err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to save preferences", err)
}
return response.Success("Preferences updated")
}
func GetPreferencesFor(ctx context.Context,
dashboardService dashboards.DashboardService, preferenceService pref.Service,
orgID, userID, teamID int64) response.Response {
prefsQuery := pref.GetPreferenceQuery{UserID: userID, OrgID: orgID, TeamID: teamID}
preference, err := preferenceService.Get(ctx, &prefsQuery)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get preferences", err)
}
var dashboardUID string
// when homedashboardID is 0, that means it is the default home dashboard, no UID would be returned in the response
if preference.HomeDashboardID != 0 {
query := dashboards.GetDashboardQuery{ID: preference.HomeDashboardID, OrgID: orgID}
queryResult, err := dashboardService.GetDashboard(ctx, &query)
if err == nil {
dashboardUID = queryResult.UID
}
}
dto := preferences.Spec{}
if preference.WeekStart != nil && *preference.WeekStart != "" {
dto.WeekStart = preference.WeekStart
}
if preference.Theme != "" {
dto.Theme = &preference.Theme
}
if dashboardUID != "" {
dto.HomeDashboardUID = &dashboardUID
}
if preference.Timezone != "" {
dto.Timezone = &preference.Timezone
}
if preference.JSONData != nil {
if preference.JSONData.Language != "" {
dto.Language = &preference.JSONData.Language
}
if preference.JSONData.QueryHistory.HomeTab != "" {
dto.QueryHistory = &preferences.QueryHistoryPreference{
HomeTab: &preference.JSONData.QueryHistory.HomeTab,
}
}
}
return response.JSON(http.StatusOK, &dto)
}

View File

@ -0,0 +1,82 @@
package teamapi
import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware/requestmeta"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/licensing"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/setting"
)
type TeamAPI struct {
teamService team.Service
ac accesscontrol.Service
teamPermissionsService accesscontrol.TeamPermissionsService
license licensing.Licensing
cfg *setting.Cfg
preferenceService pref.Service
ds dashboards.DashboardService
}
func ProvideTeamAPI(
routeRegister routing.RouteRegister,
teamService team.Service,
ac accesscontrol.Service,
acEvaluator accesscontrol.AccessControl,
teamPermissionsService accesscontrol.TeamPermissionsService,
license licensing.Licensing,
cfg *setting.Cfg,
preferenceService pref.Service,
ds dashboards.DashboardService,
) *TeamAPI {
tapi := &TeamAPI{
teamService: teamService,
ac: ac,
teamPermissionsService: teamPermissionsService,
license: license,
cfg: cfg,
preferenceService: preferenceService,
ds: ds,
}
tapi.registerRoutes(routeRegister, acEvaluator)
return tapi
}
func (tapi *TeamAPI) registerRoutes(router routing.RouteRegister, ac accesscontrol.AccessControl) {
authorize := accesscontrol.Middleware(ac)
router.Group("/api", func(apiRoute routing.RouteRegister) {
// team (admin permission required)
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
teamsRoute.Post("/", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsCreate)),
routing.Wrap(tapi.createTeam))
teamsRoute.Put("/:teamId", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsWrite,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.updateTeam))
teamsRoute.Delete("/:teamId", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsDelete,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.deleteTeamByID))
teamsRoute.Get("/:teamId/members", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsRead,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.getTeamMembers))
teamsRoute.Post("/:teamId/members", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.addTeamMember))
teamsRoute.Put("/:teamId/members/:userId", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.updateTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.removeTeamMember))
teamsRoute.Get("/:teamId/preferences", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.getTeamPreferences))
teamsRoute.Put("/:teamId/preferences", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsWrite,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.updateTeamPreferences))
}, requestmeta.SetOwner(requestmeta.TeamAuth))
// team without requirement of user to be org admin
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
teamsRoute.Get("/:teamId", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead,
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.getTeamByID))
teamsRoute.Get("/search", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead)),
routing.Wrap(tapi.searchTeams))
}, requestmeta.SetOwner(requestmeta.TeamAuth))
})
}

View File

@ -1,4 +1,4 @@
package api
package teamapi
import (
"errors"
@ -7,9 +7,11 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/preference/prefapi"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/sortopts"
"github.com/grafana/grafana/pkg/util"
@ -26,13 +28,13 @@ import (
// 403: forbiddenError
// 409: conflictError
// 500: internalServerError
func (hs *HTTPServer) CreateTeam(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) createTeam(c *contextmodel.ReqContext) response.Response {
cmd := team.CreateTeamCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
t, err := hs.teamService.CreateTeam(cmd.Name, cmd.Email, c.SignedInUser.GetOrgID())
t, err := tapi.teamService.CreateTeam(cmd.Name, cmd.Email, c.SignedInUser.GetOrgID())
if err != nil {
if errors.Is(err, team.ErrTeamNameTaken) {
return response.Error(http.StatusConflict, "Team name taken", err)
@ -42,7 +44,7 @@ func (hs *HTTPServer) CreateTeam(c *contextmodel.ReqContext) response.Response {
// Clear permission cache for the user who's created the team, so that new permissions are fetched for their next call
// Required for cases when caller wants to immediately interact with the newly created object
hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
tapi.ac.ClearUserPermissionCache(c.SignedInUser)
// if the request is authenticated using API tokens
// the SignedInUser is an empty struct therefore
@ -55,7 +57,7 @@ func (hs *HTTPServer) CreateTeam(c *contextmodel.ReqContext) response.Response {
c.Logger.Error("Could not add creator to team because user id is not a number", "error", err)
break
}
if err := addOrUpdateTeamMember(c.Req.Context(), hs.teamPermissionsService, userID, c.SignedInUser.GetOrgID(),
if err := addOrUpdateTeamMember(c.Req.Context(), tapi.teamPermissionsService, userID, c.SignedInUser.GetOrgID(),
t.ID, dashboards.PERMISSION_ADMIN.String()); err != nil {
c.Logger.Error("Could not add creator to team", "error", err)
}
@ -80,7 +82,7 @@ func (hs *HTTPServer) CreateTeam(c *contextmodel.ReqContext) response.Response {
// 404: notFoundError
// 409: conflictError
// 500: internalServerError
func (hs *HTTPServer) UpdateTeam(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) updateTeam(c *contextmodel.ReqContext) response.Response {
cmd := team.UpdateTeamCommand{}
var err error
if err := web.Bind(c.Req, &cmd); err != nil {
@ -92,7 +94,7 @@ func (hs *HTTPServer) UpdateTeam(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
}
if err := hs.teamService.UpdateTeam(c.Req.Context(), &cmd); err != nil {
if err := tapi.teamService.UpdateTeam(c.Req.Context(), &cmd); err != nil {
if errors.Is(err, team.ErrTeamNameTaken) {
return response.Error(http.StatusBadRequest, "Team name taken", err)
}
@ -112,14 +114,14 @@ func (hs *HTTPServer) UpdateTeam(c *contextmodel.ReqContext) response.Response {
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) DeleteTeamByID(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) deleteTeamByID(c *contextmodel.ReqContext) response.Response {
orgID := c.SignedInUser.GetOrgID()
teamID, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
}
if err := hs.teamService.DeleteTeam(c.Req.Context(), &team.DeleteTeamCommand{OrgID: orgID, ID: teamID}); err != nil {
if err := tapi.teamService.DeleteTeam(c.Req.Context(), &team.DeleteTeamCommand{OrgID: orgID, ID: teamID}); err != nil {
if errors.Is(err, team.ErrTeamNotFound) {
return response.Error(http.StatusNotFound, "Failed to delete Team. ID not found", nil)
}
@ -137,7 +139,7 @@ func (hs *HTTPServer) DeleteTeamByID(c *contextmodel.ReqContext) response.Respon
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) SearchTeams(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) searchTeams(c *contextmodel.ReqContext) response.Response {
perPage := c.QueryInt("perpage")
if perPage <= 0 {
perPage = 1000
@ -159,11 +161,11 @@ func (hs *HTTPServer) SearchTeams(c *contextmodel.ReqContext) response.Response
Page: page,
Limit: perPage,
SignedInUser: c.SignedInUser,
HiddenUsers: hs.Cfg.HiddenUsers,
HiddenUsers: tapi.cfg.HiddenUsers,
SortOpts: sortOpts,
}
queryResult, err := hs.teamService.SearchTeams(c.Req.Context(), &query)
queryResult, err := tapi.teamService.SearchTeams(c.Req.Context(), &query)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to search Teams", err)
}
@ -174,7 +176,7 @@ func (hs *HTTPServer) SearchTeams(c *contextmodel.ReqContext) response.Response
teamIDs[strconv.FormatInt(team.ID, 10)] = true
}
metadata := hs.getMultiAccessControlMetadata(c, "teams:id:", teamIDs)
metadata := tapi.getMultiAccessControlMetadata(c, "teams:id:", teamIDs)
if len(metadata) > 0 {
for _, team := range queryResult.Teams {
team.AccessControl = metadata[strconv.FormatInt(team.ID, 10)]
@ -197,7 +199,7 @@ func (hs *HTTPServer) SearchTeams(c *contextmodel.ReqContext) response.Response
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) GetTeamByID(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) getTeamByID(c *contextmodel.ReqContext) response.Response {
teamId, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
@ -207,10 +209,10 @@ func (hs *HTTPServer) GetTeamByID(c *contextmodel.ReqContext) response.Response
OrgID: c.SignedInUser.GetOrgID(),
ID: teamId,
SignedInUser: c.SignedInUser,
HiddenUsers: hs.Cfg.HiddenUsers,
HiddenUsers: tapi.cfg.HiddenUsers,
}
queryResult, err := hs.teamService.GetTeamByID(c.Req.Context(), &query)
queryResult, err := tapi.teamService.GetTeamByID(c.Req.Context(), &query)
if err != nil {
if errors.Is(err, team.ErrTeamNotFound) {
return response.Error(http.StatusNotFound, "Team not found", err)
@ -220,7 +222,7 @@ func (hs *HTTPServer) GetTeamByID(c *contextmodel.ReqContext) response.Response
}
// Add accesscontrol metadata
queryResult.AccessControl = hs.getAccessControlMetadata(c, c.SignedInUser.GetOrgID(), "teams:id:", strconv.FormatInt(queryResult.ID, 10))
queryResult.AccessControl = tapi.getAccessControlMetadata(c, c.SignedInUser.GetOrgID(), "teams:id:", strconv.FormatInt(queryResult.ID, 10))
queryResult.AvatarURL = dtos.GetGravatarUrlWithDefault(queryResult.Email, queryResult.Name)
return response.JSON(http.StatusOK, &queryResult)
@ -234,13 +236,13 @@ func (hs *HTTPServer) GetTeamByID(c *contextmodel.ReqContext) response.Response
// 200: getPreferencesResponse
// 401: unauthorisedError
// 500: internalServerError
func (hs *HTTPServer) GetTeamPreferences(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) getTeamPreferences(c *contextmodel.ReqContext) response.Response {
teamId, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
}
return hs.getPreferencesFor(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, teamId)
return prefapi.GetPreferencesFor(c.Req.Context(), tapi.ds, tapi.preferenceService, c.SignedInUser.GetOrgID(), 0, teamId)
}
// swagger:route PUT /teams/{team_id}/preferences teams updateTeamPreferences
@ -252,7 +254,7 @@ func (hs *HTTPServer) GetTeamPreferences(c *contextmodel.ReqContext) response.Re
// 400: badRequestError
// 401: unauthorisedError
// 500: internalServerError
func (hs *HTTPServer) UpdateTeamPreferences(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) updateTeamPreferences(c *contextmodel.ReqContext) response.Response {
dtoCmd := dtos.UpdatePrefsCmd{}
if err := web.Bind(c.Req, &dtoCmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
@ -263,7 +265,7 @@ func (hs *HTTPServer) UpdateTeamPreferences(c *contextmodel.ReqContext) response
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
}
return hs.updatePreferencesFor(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, teamId, &dtoCmd)
return prefapi.UpdatePreferencesFor(c.Req.Context(), tapi.ds, tapi.preferenceService, c.SignedInUser.GetOrgID(), 0, teamId, &dtoCmd)
}
// swagger:parameters updateTeamPreferences
@ -355,3 +357,26 @@ type CreateTeamResponse struct {
Message string `json:"message"`
} `json:"body"`
}
// getMultiAccessControlMetadata returns the accesscontrol metadata associated with a given set of resources
// Context must contain permissions in the given org (see LoadPermissionsMiddleware or AuthorizeInOrgMiddleware)
func (tapi *TeamAPI) getMultiAccessControlMetadata(c *contextmodel.ReqContext,
prefix string, resourceIDs map[string]bool) map[string]accesscontrol.Metadata {
if !c.QueryBool("accesscontrol") {
return map[string]accesscontrol.Metadata{}
}
if len(c.SignedInUser.GetPermissions()) == 0 {
return map[string]accesscontrol.Metadata{}
}
return accesscontrol.GetResourcesMetadata(c.Req.Context(), c.SignedInUser.GetPermissions(), prefix, resourceIDs)
}
// Metadata helpers
// getAccessControlMetadata returns the accesscontrol metadata associated with a given resource
func (tapi *TeamAPI) getAccessControlMetadata(c *contextmodel.ReqContext,
orgID int64, prefix string, resourceID string) accesscontrol.Metadata {
ids := map[string]bool{resourceID: true}
return tapi.getMultiAccessControlMetadata(c, prefix, ids)[resourceID]
}

View File

@ -1,4 +1,4 @@
package api
package teamapi
import (
"context"
@ -28,7 +28,7 @@ import (
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) GetTeamMembers(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) getTeamMembers(c *contextmodel.ReqContext) response.Response {
teamId, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
@ -36,21 +36,21 @@ func (hs *HTTPServer) GetTeamMembers(c *contextmodel.ReqContext) response.Respon
query := team.GetTeamMembersQuery{OrgID: c.SignedInUser.GetOrgID(), TeamID: teamId, SignedInUser: c.SignedInUser}
queryResult, err := hs.teamService.GetTeamMembers(c.Req.Context(), &query)
queryResult, err := tapi.teamService.GetTeamMembers(c.Req.Context(), &query)
if err != nil {
return response.Error(500, "Failed to get Team Members", err)
return response.Error(http.StatusInternalServerError, "Failed to get Team Members", err)
}
filteredMembers := make([]*team.TeamMemberDTO, 0, len(queryResult))
for _, member := range queryResult {
if dtos.IsHiddenUser(member.Login, c.SignedInUser, hs.Cfg) {
if dtos.IsHiddenUser(member.Login, c.SignedInUser, tapi.cfg) {
continue
}
member.AvatarURL = dtos.GetGravatarUrl(member.Email)
member.Labels = []string{}
if hs.License.FeatureEnabled("teamgroupsync") && member.External {
if tapi.license.FeatureEnabled("teamgroupsync") && member.External {
authProvider := login.GetAuthProviderLabel(member.AuthModule)
member.Labels = append(member.Labels, authProvider)
}
@ -71,7 +71,7 @@ func (hs *HTTPServer) GetTeamMembers(c *contextmodel.ReqContext) response.Respon
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) AddTeamMember(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) addTeamMember(c *contextmodel.ReqContext) response.Response {
cmd := team.AddTeamMemberCommand{}
var err error
if err := web.Bind(c.Req, &cmd); err != nil {
@ -83,17 +83,17 @@ func (hs *HTTPServer) AddTeamMember(c *contextmodel.ReqContext) response.Respons
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
}
isTeamMember, err := hs.teamService.IsTeamMember(c.SignedInUser.GetOrgID(), cmd.TeamID, cmd.UserID)
isTeamMember, err := tapi.teamService.IsTeamMember(c.SignedInUser.GetOrgID(), cmd.TeamID, cmd.UserID)
if err != nil {
return response.Error(500, "Failed to add team member.", err)
return response.Error(http.StatusInternalServerError, "Failed to add team member.", err)
}
if isTeamMember {
return response.Error(400, "User is already added to this team", nil)
return response.Error(http.StatusBadRequest, "User is already added to this team", nil)
}
err = addOrUpdateTeamMember(c.Req.Context(), hs.teamPermissionsService, cmd.UserID, cmd.OrgID, cmd.TeamID, getPermissionName(cmd.Permission))
err = addOrUpdateTeamMember(c.Req.Context(), tapi.teamPermissionsService, cmd.UserID, cmd.OrgID, cmd.TeamID, getPermissionName(cmd.Permission))
if err != nil {
return response.Error(500, "Failed to add Member to Team", err)
return response.Error(http.StatusInternalServerError, "Failed to add Member to Team", err)
}
return response.JSON(http.StatusOK, &util.DynMap{
@ -111,7 +111,7 @@ func (hs *HTTPServer) AddTeamMember(c *contextmodel.ReqContext) response.Respons
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) UpdateTeamMember(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) updateTeamMember(c *contextmodel.ReqContext) response.Response {
cmd := team.UpdateTeamMemberCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
@ -126,17 +126,17 @@ func (hs *HTTPServer) UpdateTeamMember(c *contextmodel.ReqContext) response.Resp
}
orgId := c.SignedInUser.GetOrgID()
isTeamMember, err := hs.teamService.IsTeamMember(orgId, teamId, userId)
isTeamMember, err := tapi.teamService.IsTeamMember(orgId, teamId, userId)
if err != nil {
return response.Error(500, "Failed to update team member.", err)
return response.Error(http.StatusInternalServerError, "Failed to update team member.", err)
}
if !isTeamMember {
return response.Error(404, "Team member not found.", nil)
return response.Error(http.StatusNotFound, "Team member not found.", nil)
}
err = addOrUpdateTeamMember(c.Req.Context(), hs.teamPermissionsService, userId, orgId, teamId, getPermissionName(cmd.Permission))
err = addOrUpdateTeamMember(c.Req.Context(), tapi.teamPermissionsService, userId, orgId, teamId, getPermissionName(cmd.Permission))
if err != nil {
return response.Error(500, "Failed to update team member.", err)
return response.Error(http.StatusInternalServerError, "Failed to update team member.", err)
}
return response.Success("Team member updated")
}
@ -161,7 +161,7 @@ func getPermissionName(permission dashboards.PermissionType) string {
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) RemoveTeamMember(c *contextmodel.ReqContext) response.Response {
func (tapi *TeamAPI) removeTeamMember(c *contextmodel.ReqContext) response.Response {
orgId := c.SignedInUser.GetOrgID()
teamId, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
if err != nil {
@ -173,16 +173,16 @@ func (hs *HTTPServer) RemoveTeamMember(c *contextmodel.ReqContext) response.Resp
}
teamIDString := strconv.FormatInt(teamId, 10)
if _, err := hs.teamPermissionsService.SetUserPermission(c.Req.Context(), orgId, accesscontrol.User{ID: userId}, teamIDString, ""); err != nil {
if _, err := tapi.teamPermissionsService.SetUserPermission(c.Req.Context(), orgId, accesscontrol.User{ID: userId}, teamIDString, ""); err != nil {
if errors.Is(err, team.ErrTeamNotFound) {
return response.Error(404, "Team not found", nil)
return response.Error(http.StatusNotFound, "Team not found", nil)
}
if errors.Is(err, team.ErrTeamMemberNotFound) {
return response.Error(404, "Team member not found", nil)
return response.Error(http.StatusNotFound, "Team member not found", nil)
}
return response.Error(500, "Failed to remove Member from Team", err)
return response.Error(http.StatusInternalServerError, "Failed to remove Member from Team", err)
}
return response.Success("Team Member removed")
}

View File

@ -1,4 +1,4 @@
package api
package teamapi
import (
"net/http"
@ -8,24 +8,52 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/preference/preftest"
"github.com/grafana/grafana/pkg/services/team/teamtest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web/webtest"
)
func SetupAPITestServer(t *testing.T, opts ...func(a *TeamAPI)) *webtest.Server {
t.Helper()
router := routing.NewRouteRegister()
cfg := setting.NewCfg()
cfg.LDAPAuthEnabled = true
a := ProvideTeamAPI(router,
teamtest.NewFakeService(),
actest.FakeService{},
acimpl.ProvideAccessControl(cfg),
&actest.FakePermissionsService{},
&licensing.OSSLicensingService{},
cfg,
preftest.NewPreferenceServiceFake(),
dashboards.NewFakeDashboardService(t),
)
for _, o := range opts {
o(a)
}
server := webtest.NewServer(t, router)
return server
}
func TestAddTeamMembersAPIEndpoint(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.teamService = teamtest.NewFakeService()
hs.teamPermissionsService = &actest.FakePermissionsService{}
})
server := SetupAPITestServer(t)
t.Run("should be able to add team member with correct permission", func(t *testing.T) {
req := webtest.RequestWithSignedInUser(
server.NewRequest(http.MethodPost, "/api/teams/1/members", strings.NewReader("{\"userId\": 1}")),
userWithPermissions(1, []ac.Permission{{Action: ac.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
)
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -36,7 +64,7 @@ func TestAddTeamMembersAPIEndpoint(t *testing.T) {
t.Run("should not be able to add team member without correct permission", func(t *testing.T) {
req := webtest.RequestWithSignedInUser(
server.NewRequest(http.MethodPost, "/api/teams/1/members", strings.NewReader("{\"userId\": 1}")),
userWithPermissions(1, []ac.Permission{{Action: ac.ActionTeamsPermissionsWrite, Scope: "teams:id:2"}}),
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:2"}}),
)
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -46,16 +74,12 @@ func TestAddTeamMembersAPIEndpoint(t *testing.T) {
}
func TestGetTeamMembersAPIEndpoint(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.teamService = teamtest.NewFakeService()
hs.teamPermissionsService = &actest.FakePermissionsService{}
})
server := SetupAPITestServer(t)
t.Run("should be able to get team members with correct permission", func(t *testing.T) {
req := webtest.RequestWithSignedInUser(
server.NewGetRequest("/api/teams/1/members"),
userWithPermissions(1, []ac.Permission{{Action: ac.ActionTeamsPermissionsRead, Scope: "teams:id:1"}}),
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"}}),
)
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -65,7 +89,7 @@ func TestGetTeamMembersAPIEndpoint(t *testing.T) {
t.Run("should not be able to get team members without correct permission", func(t *testing.T) {
req := webtest.RequestWithSignedInUser(
server.NewGetRequest("/api/teams/1/members"),
userWithPermissions(1, []ac.Permission{{Action: ac.ActionTeamsPermissionsRead, Scope: "teams:id:2"}}),
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:2"}}),
)
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -75,16 +99,14 @@ func TestGetTeamMembersAPIEndpoint(t *testing.T) {
}
func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.teamService = &teamtest.FakeService{ExpectedIsMember: true}
hs.teamPermissionsService = &actest.FakePermissionsService{}
})
t.Run("should be able to update team member with correct permission", func(t *testing.T) {
req := webtest.RequestWithSignedInUser(
server.NewRequest(http.MethodPut, "/api/teams/1/members/1", strings.NewReader("{\"permission\": 1}")),
userWithPermissions(1, []ac.Permission{{Action: ac.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
)
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -94,7 +116,7 @@ func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
t.Run("should not be able to update team member without correct permission", func(t *testing.T) {
req := webtest.RequestWithSignedInUser(
server.NewRequest(http.MethodPut, "/api/teams/1/members/1", strings.NewReader("{\"permission\": 1}")),
userWithPermissions(1, []ac.Permission{{Action: ac.ActionTeamsPermissionsWrite, Scope: "teams:id:2"}}),
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:2"}}),
)
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -104,8 +126,7 @@ func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
}
func TestDeleteTeamMembersAPIEndpoint(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.teamService = &teamtest.FakeService{ExpectedIsMember: true}
hs.teamPermissionsService = &actest.FakePermissionsService{}
})
@ -113,7 +134,7 @@ func TestDeleteTeamMembersAPIEndpoint(t *testing.T) {
t.Run("should be able to delete team member with correct permission", func(t *testing.T) {
req := webtest.RequestWithSignedInUser(
server.NewRequest(http.MethodDelete, "/api/teams/1/members/1", nil),
userWithPermissions(1, []ac.Permission{{Action: ac.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
)
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -123,7 +144,7 @@ func TestDeleteTeamMembersAPIEndpoint(t *testing.T) {
t.Run("should not be able to delete member without correct permission", func(t *testing.T) {
req := webtest.RequestWithSignedInUser(
server.NewRequest(http.MethodDelete, "/api/teams/1/members/1", nil),
userWithPermissions(1, []ac.Permission{{Action: ac.ActionTeamsPermissionsWrite, Scope: "teams:id:2"}}),
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:2"}}),
)
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -131,3 +152,7 @@ func TestDeleteTeamMembersAPIEndpoint(t *testing.T) {
require.NoError(t, res.Body.Close())
})
}
func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
}

View File

@ -1,4 +1,4 @@
package api
package teamapi
import (
"fmt"
@ -10,14 +10,11 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/services/preference/preftest"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/teamtest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web/webtest"
)
@ -31,17 +28,14 @@ const (
)
func TestTeamAPIEndpoint_CreateTeam(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.teamService = teamtest.NewFakeService()
hs.AccessControl = acimpl.ProvideAccessControl(setting.NewCfg())
hs.accesscontrolService = actest.FakeService{}
})
input := strings.NewReader(fmt.Sprintf(teamCmd, 1))
t.Run("Access control allows creating teams with the correct permissions", func(t *testing.T) {
req := server.NewPostRequest(createTeamURL, input)
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsCreate}}))
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsCreate}}))
res, err := server.SendJSON(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode)
@ -51,7 +45,7 @@ func TestTeamAPIEndpoint_CreateTeam(t *testing.T) {
input = strings.NewReader(fmt.Sprintf(teamCmd, 2))
t.Run("Access control prevents creating teams with the incorrect permissions", func(t *testing.T) {
req := server.NewPostRequest(createTeamURL, input)
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, []accesscontrol.Permission{}))
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{}))
res, err := server.SendJSON(req)
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
@ -60,14 +54,13 @@ func TestTeamAPIEndpoint_CreateTeam(t *testing.T) {
}
func TestTeamAPIEndpoint_SearchTeams(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.teamService = teamtest.NewFakeService()
})
t.Run("Access control prevents searching for teams with the incorrect permissions", func(t *testing.T) {
req := server.NewGetRequest(searchTeamsURL)
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, []accesscontrol.Permission{}))
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{}))
res, err := server.Send(req)
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
@ -76,7 +69,7 @@ func TestTeamAPIEndpoint_SearchTeams(t *testing.T) {
t.Run("Access control allows searching for teams with the correct permissions", func(t *testing.T) {
req := server.NewGetRequest(searchTeamsURL)
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, []accesscontrol.Permission{
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: accesscontrol.ScopeTeamsAll},
}))
res, err := server.Send(req)
@ -87,8 +80,7 @@ func TestTeamAPIEndpoint_SearchTeams(t *testing.T) {
}
func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
})
@ -96,7 +88,7 @@ func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
t.Run("Access control prevents getting a team when missing permissions", func(t *testing.T) {
req := server.NewGetRequest(url)
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, []accesscontrol.Permission{}))
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{}))
res, err := server.Send(req)
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, res.StatusCode)
@ -105,7 +97,7 @@ func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
t.Run("Access control allows getting a team with the correct permissions", func(t *testing.T) {
req := server.NewGetRequest(url)
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, []accesscontrol.Permission{
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
}))
res, err := server.Send(req)
@ -116,7 +108,7 @@ func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
t.Run("Access control allows getting a team with wildcard scope", func(t *testing.T) {
req := server.NewGetRequest(url)
req = webtest.RequestWithSignedInUser(req, userWithPermissions(1, []accesscontrol.Permission{
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:*"},
}))
res, err := server.Send(req)
@ -130,8 +122,7 @@ func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
// Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsWrite with teams:id:1 scope
// else return 403
func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
})
@ -142,7 +133,7 @@ func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
}
t.Run("Access control allows updating team with the correct permissions", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:id:1"},
}))
require.NoError(t, err)
@ -151,7 +142,7 @@ func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
})
t.Run("Access control allows updating teams with the wildcard scope", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:*"},
}))
require.NoError(t, err)
@ -160,7 +151,7 @@ func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
})
t.Run("Access control prevent updating a team with wrong scope", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:id:2"},
}))
require.NoError(t, err)
@ -173,8 +164,7 @@ func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
// Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsDelete with teams:id:1 scope
// else return 403
func TestTeamAPIEndpoint_DeleteTeam(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
})
@ -185,7 +175,7 @@ func TestTeamAPIEndpoint_DeleteTeam(t *testing.T) {
}
t.Run("Access control prevents deleting teams with the incorrect permissions", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsDelete, Scope: "teams:id:2"},
}))
require.NoError(t, err)
@ -194,7 +184,7 @@ func TestTeamAPIEndpoint_DeleteTeam(t *testing.T) {
})
t.Run("Access control allows deleting teams with the correct permissions", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsDelete, Scope: "teams:id:1"},
}))
require.NoError(t, err)
@ -207,8 +197,7 @@ func TestTeamAPIEndpoint_DeleteTeam(t *testing.T) {
// Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsRead with teams:id:1 scope
// else return 403
func TestTeamAPIEndpoint_GetTeamPreferences(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.preferenceService = &preftest.FakePreferenceService{ExpectedPreference: &pref.Preference{}}
})
@ -219,7 +208,7 @@ func TestTeamAPIEndpoint_GetTeamPreferences(t *testing.T) {
}
t.Run("Access control allows getting team preferences with the correct permissions", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
}))
require.NoError(t, err)
@ -228,7 +217,7 @@ func TestTeamAPIEndpoint_GetTeamPreferences(t *testing.T) {
})
t.Run("Access control prevents getting team preferences with the incorrect permissions", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
}))
require.NoError(t, err)
@ -241,8 +230,7 @@ func TestTeamAPIEndpoint_GetTeamPreferences(t *testing.T) {
// Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsWrite with teams:id:1 scope
// else return 403
func TestTeamAPIEndpoint_UpdateTeamPreferences(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
server := SetupAPITestServer(t, func(hs *TeamAPI) {
hs.preferenceService = &preftest.FakePreferenceService{ExpectedPreference: &pref.Preference{}}
})
@ -253,7 +241,7 @@ func TestTeamAPIEndpoint_UpdateTeamPreferences(t *testing.T) {
}
t.Run("Access control allows updating team preferences with the correct permissions", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:id:1"},
}))
require.NoError(t, err)
@ -262,7 +250,7 @@ func TestTeamAPIEndpoint_UpdateTeamPreferences(t *testing.T) {
})
t.Run("Access control prevents updating team preferences with the incorrect permissions", func(t *testing.T) {
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:id:2"},
}))
require.NoError(t, err)