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:
@@ -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))
|
||||
|
||||
@@ -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)}}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+6
-94
@@ -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
|
||||
|
||||
-357
@@ -1,357 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"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/team"
|
||||
"github.com/grafana/grafana/pkg/services/team/sortopts"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
// swagger:route POST /teams teams createTeam
|
||||
//
|
||||
// Add Team.
|
||||
//
|
||||
// Responses:
|
||||
// 200: createTeamResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 409: conflictError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) 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())
|
||||
if err != nil {
|
||||
if errors.Is(err, team.ErrTeamNameTaken) {
|
||||
return response.Error(http.StatusConflict, "Team name taken", err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to create Team", err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// if the request is authenticated using API tokens
|
||||
// the SignedInUser is an empty struct therefore
|
||||
// an additional check whether it is an actual user is required
|
||||
namespace, identifier := c.SignedInUser.GetNamespacedID()
|
||||
switch namespace {
|
||||
case identity.NamespaceUser:
|
||||
userID, err := strconv.ParseInt(identifier, 10, 64)
|
||||
if err != nil {
|
||||
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(),
|
||||
t.ID, dashboards.PERMISSION_ADMIN.String()); err != nil {
|
||||
c.Logger.Error("Could not add creator to team", "error", err)
|
||||
}
|
||||
default:
|
||||
c.Logger.Warn("Could not add creator to team because is not a real user")
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, &util.DynMap{
|
||||
"teamId": t.ID,
|
||||
"message": "Team created",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:route PUT /teams/{team_id} teams updateTeam
|
||||
//
|
||||
// Update Team.
|
||||
//
|
||||
// Responses:
|
||||
// 200: okResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 409: conflictError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) UpdateTeam(c *contextmodel.ReqContext) response.Response {
|
||||
cmd := team.UpdateTeamCommand{}
|
||||
var err error
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
cmd.OrgID = c.SignedInUser.GetOrgID()
|
||||
cmd.ID, 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.UpdateTeam(c.Req.Context(), &cmd); err != nil {
|
||||
if errors.Is(err, team.ErrTeamNameTaken) {
|
||||
return response.Error(http.StatusBadRequest, "Team name taken", err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to update Team", err)
|
||||
}
|
||||
|
||||
return response.Success("Team updated")
|
||||
}
|
||||
|
||||
// swagger:route DELETE /teams/{team_id} teams deleteTeamByID
|
||||
//
|
||||
// Delete Team By ID.
|
||||
//
|
||||
// Responses:
|
||||
// 200: okResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) 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 errors.Is(err, team.ErrTeamNotFound) {
|
||||
return response.Error(http.StatusNotFound, "Failed to delete Team. ID not found", nil)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to delete Team", err)
|
||||
}
|
||||
return response.Success("Team deleted")
|
||||
}
|
||||
|
||||
// swagger:route GET /teams/search teams searchTeams
|
||||
//
|
||||
// Team Search With Paging.
|
||||
//
|
||||
// Responses:
|
||||
// 200: searchTeamsResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) SearchTeams(c *contextmodel.ReqContext) response.Response {
|
||||
perPage := c.QueryInt("perpage")
|
||||
if perPage <= 0 {
|
||||
perPage = 1000
|
||||
}
|
||||
page := c.QueryInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
sortOpts, err := sortopts.ParseSortQueryParam(c.Query("sort"))
|
||||
if err != nil {
|
||||
return response.Err(err)
|
||||
}
|
||||
|
||||
query := team.SearchTeamsQuery{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
Query: c.Query("query"),
|
||||
Name: c.Query("name"),
|
||||
Page: page,
|
||||
Limit: perPage,
|
||||
SignedInUser: c.SignedInUser,
|
||||
HiddenUsers: hs.Cfg.HiddenUsers,
|
||||
SortOpts: sortOpts,
|
||||
}
|
||||
|
||||
queryResult, err := hs.teamService.SearchTeams(c.Req.Context(), &query)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to search Teams", err)
|
||||
}
|
||||
|
||||
teamIDs := map[string]bool{}
|
||||
for _, team := range queryResult.Teams {
|
||||
team.AvatarURL = dtos.GetGravatarUrlWithDefault(team.Email, team.Name)
|
||||
teamIDs[strconv.FormatInt(team.ID, 10)] = true
|
||||
}
|
||||
|
||||
metadata := hs.getMultiAccessControlMetadata(c, "teams:id:", teamIDs)
|
||||
if len(metadata) > 0 {
|
||||
for _, team := range queryResult.Teams {
|
||||
team.AccessControl = metadata[strconv.FormatInt(team.ID, 10)]
|
||||
}
|
||||
}
|
||||
|
||||
queryResult.Page = page
|
||||
queryResult.PerPage = perPage
|
||||
|
||||
return response.JSON(http.StatusOK, queryResult)
|
||||
}
|
||||
|
||||
// swagger:route GET /teams/{team_id} teams getTeamByID
|
||||
//
|
||||
// Get Team By ID.
|
||||
//
|
||||
// Responses:
|
||||
// 200: getTeamByIDResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) 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)
|
||||
}
|
||||
|
||||
query := team.GetTeamByIDQuery{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
ID: teamId,
|
||||
SignedInUser: c.SignedInUser,
|
||||
HiddenUsers: hs.Cfg.HiddenUsers,
|
||||
}
|
||||
|
||||
queryResult, err := hs.teamService.GetTeamByID(c.Req.Context(), &query)
|
||||
if err != nil {
|
||||
if errors.Is(err, team.ErrTeamNotFound) {
|
||||
return response.Error(http.StatusNotFound, "Team not found", err)
|
||||
}
|
||||
|
||||
return response.Error(http.StatusInternalServerError, "Failed to get Team", err)
|
||||
}
|
||||
|
||||
// Add accesscontrol metadata
|
||||
queryResult.AccessControl = hs.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)
|
||||
}
|
||||
|
||||
// swagger:route GET /teams/{team_id}/preferences teams getTeamPreferences
|
||||
//
|
||||
// Get Team Preferences.
|
||||
//
|
||||
// Responses:
|
||||
// 200: getPreferencesResponse
|
||||
// 401: unauthorisedError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) 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)
|
||||
}
|
||||
|
||||
// swagger:route PUT /teams/{team_id}/preferences teams updateTeamPreferences
|
||||
//
|
||||
// Update Team Preferences.
|
||||
//
|
||||
// Responses:
|
||||
// 200: okResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) 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)
|
||||
}
|
||||
|
||||
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.updatePreferencesFor(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, teamId, &dtoCmd)
|
||||
}
|
||||
|
||||
// swagger:parameters updateTeamPreferences
|
||||
type UpdateTeamPreferencesParams struct {
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
// in:body
|
||||
// required:true
|
||||
Body dtos.UpdatePrefsCmd `json:"body"`
|
||||
}
|
||||
|
||||
// swagger:parameters getTeamByID
|
||||
type GetTeamByIDParams struct {
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
}
|
||||
|
||||
// swagger:parameters deleteTeamByID
|
||||
type DeleteTeamByIDParams struct {
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
}
|
||||
|
||||
// swagger:parameters getTeamPreferences
|
||||
type GetTeamPreferencesParams struct {
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
}
|
||||
|
||||
// swagger:parameters searchTeams
|
||||
type SearchTeamsParams struct {
|
||||
// in:query
|
||||
// required:false
|
||||
// default: 1
|
||||
Page int `json:"page"`
|
||||
// Number of items per page
|
||||
// The totalCount field in the response can be used for pagination list E.g. if totalCount is equal to 100 teams and the perpage parameter is set to 10 then there are 10 pages of teams.
|
||||
// in:query
|
||||
// required:false
|
||||
// default: 1000
|
||||
PerPage int `json:"perpage"`
|
||||
Name string `json:"name"`
|
||||
// If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded.
|
||||
// required:false
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
// swagger:parameters createTeam
|
||||
type CreateTeamParams struct {
|
||||
// in:body
|
||||
// required:true
|
||||
Body team.CreateTeamCommand `json:"body"`
|
||||
}
|
||||
|
||||
// swagger:parameters updateTeam
|
||||
type UpdateTeamParams struct {
|
||||
// in:body
|
||||
// required:true
|
||||
Body team.UpdateTeamCommand `json:"body"`
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
}
|
||||
|
||||
// swagger:response searchTeamsResponse
|
||||
type SearchTeamsResponse struct {
|
||||
// The response message
|
||||
// in: body
|
||||
Body team.SearchTeamQueryResult `json:"body"`
|
||||
}
|
||||
|
||||
// swagger:response getTeamByIDResponse
|
||||
type GetTeamByIDResponse struct {
|
||||
// The response message
|
||||
// in: body
|
||||
Body *team.TeamDTO `json:"body"`
|
||||
}
|
||||
|
||||
// swagger:response createTeamResponse
|
||||
type CreateTeamResponse struct {
|
||||
// The response message
|
||||
// in: body
|
||||
Body struct {
|
||||
TeamId int64 `json:"teamId"`
|
||||
Message string `json:"message"`
|
||||
} `json:"body"`
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
// swagger:route GET /teams/{team_id}/members teams getTeamMembers
|
||||
//
|
||||
// Get Team Members.
|
||||
//
|
||||
// Responses:
|
||||
// 200: getTeamMembersResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) 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)
|
||||
}
|
||||
|
||||
query := team.GetTeamMembersQuery{OrgID: c.SignedInUser.GetOrgID(), TeamID: teamId, SignedInUser: c.SignedInUser}
|
||||
|
||||
queryResult, err := hs.teamService.GetTeamMembers(c.Req.Context(), &query)
|
||||
if err != nil {
|
||||
return response.Error(500, "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) {
|
||||
continue
|
||||
}
|
||||
|
||||
member.AvatarURL = dtos.GetGravatarUrl(member.Email)
|
||||
member.Labels = []string{}
|
||||
|
||||
if hs.License.FeatureEnabled("teamgroupsync") && member.External {
|
||||
authProvider := login.GetAuthProviderLabel(member.AuthModule)
|
||||
member.Labels = append(member.Labels, authProvider)
|
||||
}
|
||||
|
||||
filteredMembers = append(filteredMembers, member)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, filteredMembers)
|
||||
}
|
||||
|
||||
// swagger:route POST /teams/{team_id}/members teams addTeamMember
|
||||
//
|
||||
// Add Team Member.
|
||||
//
|
||||
// Responses:
|
||||
// 200: okResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) AddTeamMember(c *contextmodel.ReqContext) response.Response {
|
||||
cmd := team.AddTeamMemberCommand{}
|
||||
var err error
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
cmd.OrgID = c.SignedInUser.GetOrgID()
|
||||
cmd.TeamID, err = strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
|
||||
}
|
||||
|
||||
isTeamMember, err := hs.teamService.IsTeamMember(c.SignedInUser.GetOrgID(), cmd.TeamID, cmd.UserID)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to add team member.", err)
|
||||
}
|
||||
if isTeamMember {
|
||||
return response.Error(400, "User is already added to this team", nil)
|
||||
}
|
||||
|
||||
err = addOrUpdateTeamMember(c.Req.Context(), hs.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.JSON(http.StatusOK, &util.DynMap{
|
||||
"message": "Member added to Team",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:route PUT /teams/{team_id}/members/{user_id} teams updateTeamMember
|
||||
//
|
||||
// Update Team Member.
|
||||
//
|
||||
// Responses:
|
||||
// 200: okResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) 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)
|
||||
}
|
||||
teamId, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
|
||||
}
|
||||
userId, err := strconv.ParseInt(web.Params(c.Req)[":userId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "userId is invalid", err)
|
||||
}
|
||||
orgId := c.SignedInUser.GetOrgID()
|
||||
|
||||
isTeamMember, err := hs.teamService.IsTeamMember(orgId, teamId, userId)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to update team member.", err)
|
||||
}
|
||||
if !isTeamMember {
|
||||
return response.Error(404, "Team member not found.", nil)
|
||||
}
|
||||
|
||||
err = addOrUpdateTeamMember(c.Req.Context(), hs.teamPermissionsService, userId, orgId, teamId, getPermissionName(cmd.Permission))
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to update team member.", err)
|
||||
}
|
||||
return response.Success("Team member updated")
|
||||
}
|
||||
|
||||
func getPermissionName(permission dashboards.PermissionType) string {
|
||||
permissionName := permission.String()
|
||||
// Team member permission is 0, which maps to an empty string.
|
||||
// However, we want the team permission service to display "Member" for team members. This is a hack to make it work.
|
||||
if permissionName == "" {
|
||||
permissionName = "Member"
|
||||
}
|
||||
return permissionName
|
||||
}
|
||||
|
||||
// swagger:route DELETE /teams/{team_id}/members/{user_id} teams removeTeamMember
|
||||
//
|
||||
// Remove Member From Team.
|
||||
//
|
||||
// Responses:
|
||||
// 200: okResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) RemoveTeamMember(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)
|
||||
}
|
||||
userId, err := strconv.ParseInt(web.Params(c.Req)[":userId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "userId is invalid", err)
|
||||
}
|
||||
|
||||
teamIDString := strconv.FormatInt(teamId, 10)
|
||||
if _, err := hs.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)
|
||||
}
|
||||
|
||||
if errors.Is(err, team.ErrTeamMemberNotFound) {
|
||||
return response.Error(404, "Team member not found", nil)
|
||||
}
|
||||
|
||||
return response.Error(500, "Failed to remove Member from Team", err)
|
||||
}
|
||||
return response.Success("Team Member removed")
|
||||
}
|
||||
|
||||
// addOrUpdateTeamMember adds or updates a team member.
|
||||
//
|
||||
// Stubbable by tests.
|
||||
var addOrUpdateTeamMember = func(ctx context.Context, resourcePermissionService accesscontrol.TeamPermissionsService, userID, orgID, teamID int64, permission string) error {
|
||||
teamIDString := strconv.FormatInt(teamID, 10)
|
||||
if _, err := resourcePermissionService.SetUserPermission(ctx, orgID, accesscontrol.User{ID: userID}, teamIDString, permission); err != nil {
|
||||
return fmt.Errorf("failed setting permissions for user %d in team %d: %w", userID, teamID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// swagger:parameters getTeamMembers
|
||||
type GetTeamMembersParams struct {
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
}
|
||||
|
||||
// swagger:parameters addTeamMember
|
||||
type AddTeamMemberParams struct {
|
||||
// in:body
|
||||
// required:true
|
||||
Body team.AddTeamMemberCommand `json:"body"`
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
}
|
||||
|
||||
// swagger:parameters updateTeamMember
|
||||
type UpdateTeamMemberParams struct {
|
||||
// in:body
|
||||
// required:true
|
||||
Body team.UpdateTeamMemberCommand `json:"body"`
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
// in:path
|
||||
// required:true
|
||||
UserID int64 `json:"user_id"`
|
||||
}
|
||||
|
||||
// swagger:parameters removeTeamMember
|
||||
type RemoveTeamMemberParams struct {
|
||||
// in:path
|
||||
// required:true
|
||||
TeamID string `json:"team_id"`
|
||||
// in:path
|
||||
// required:true
|
||||
UserID int64 `json:"user_id"`
|
||||
}
|
||||
|
||||
// swagger:response getTeamMembersResponse
|
||||
type GetTeamMembersResponse struct {
|
||||
// The response message
|
||||
// in: body
|
||||
Body []*team.TeamMemberDTO `json:"body"`
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web/webtest"
|
||||
)
|
||||
|
||||
func TestAddTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.teamService = teamtest.NewFakeService()
|
||||
hs.teamPermissionsService = &actest.FakePermissionsService{}
|
||||
})
|
||||
|
||||
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"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
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"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.teamService = teamtest.NewFakeService()
|
||||
hs.teamPermissionsService = &actest.FakePermissionsService{}
|
||||
})
|
||||
|
||||
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"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
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"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
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"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
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"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.teamService = &teamtest.FakeService{ExpectedIsMember: true}
|
||||
hs.teamPermissionsService = &actest.FakePermissionsService{}
|
||||
})
|
||||
|
||||
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"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
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"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
searchTeamsURL = "/api/teams/search"
|
||||
createTeamURL = "/api/teams/"
|
||||
detailTeamURL = "/api/teams/%d"
|
||||
detailTeamPreferenceURL = "/api/teams/%d/preferences"
|
||||
teamCmd = `{"name": "MyTestTeam%d"}`
|
||||
teamPreferenceCmd = `{"theme": "dark"}`
|
||||
)
|
||||
|
||||
func TestTeamAPIEndpoint_CreateTeam(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
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}}))
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
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{}))
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
func TestTeamAPIEndpoint_SearchTeams(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
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{}))
|
||||
res, err := server.Send(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
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{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: accesscontrol.ScopeTeamsAll},
|
||||
}))
|
||||
res, err := server.Send(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
||||
})
|
||||
|
||||
url := fmt.Sprintf(detailTeamURL, 1)
|
||||
|
||||
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{}))
|
||||
res, err := server.Send(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
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{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
|
||||
}))
|
||||
res, err := server.Send(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
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{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:*"},
|
||||
}))
|
||||
res, err := server.Send(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
// Given a team with a user, when the user is granted X permission,
|
||||
// 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()
|
||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
||||
})
|
||||
|
||||
request := func(teamID int64, user *user.SignedInUser) (*http.Response, error) {
|
||||
req := server.NewRequest(http.MethodPut, fmt.Sprintf(detailTeamURL, teamID), strings.NewReader(teamCmd))
|
||||
req = webtest.RequestWithSignedInUser(req, user)
|
||||
return server.SendJSON(req)
|
||||
}
|
||||
|
||||
t.Run("Access control allows updating team with the correct permissions", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:id:1"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control allows updating teams with the wildcard scope", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:*"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control prevent updating a team with wrong scope", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:id:2"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
// Given a team with a user, when the user is granted X permission,
|
||||
// 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()
|
||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
||||
})
|
||||
|
||||
request := func(teamID int64, user *user.SignedInUser) (*http.Response, error) {
|
||||
req := server.NewRequest(http.MethodDelete, fmt.Sprintf(detailTeamURL, teamID), http.NoBody)
|
||||
req = webtest.RequestWithSignedInUser(req, user)
|
||||
return server.Send(req)
|
||||
}
|
||||
|
||||
t.Run("Access control prevents deleting teams with the incorrect permissions", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsDelete, Scope: "teams:id:2"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control allows deleting teams with the correct permissions", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsDelete, Scope: "teams:id:1"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
// Given a team with a user, when the user is granted X permission,
|
||||
// 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()
|
||||
hs.preferenceService = &preftest.FakePreferenceService{ExpectedPreference: &pref.Preference{}}
|
||||
})
|
||||
|
||||
request := func(teamID int64, user *user.SignedInUser) (*http.Response, error) {
|
||||
req := server.NewGetRequest(fmt.Sprintf(detailTeamPreferenceURL, teamID))
|
||||
req = webtest.RequestWithSignedInUser(req, user)
|
||||
return server.Send(req)
|
||||
}
|
||||
|
||||
t.Run("Access control allows getting team preferences with the correct permissions", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control prevents getting team preferences with the incorrect permissions", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
// Given a team with a user, when the user is granted X permission,
|
||||
// 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()
|
||||
hs.preferenceService = &preftest.FakePreferenceService{ExpectedPreference: &pref.Preference{}}
|
||||
})
|
||||
|
||||
request := func(teamID int64, user *user.SignedInUser) (*http.Response, error) {
|
||||
req := server.NewRequest(http.MethodPut, fmt.Sprintf(detailTeamPreferenceURL, teamID), strings.NewReader(teamPreferenceCmd))
|
||||
req = webtest.RequestWithSignedInUser(req, user)
|
||||
return server.SendJSON(req)
|
||||
}
|
||||
|
||||
t.Run("Access control allows updating team preferences with the correct permissions", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:id:1"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control prevents updating team preferences with the incorrect permissions", func(t *testing.T) {
|
||||
res, err := request(1, userWithPermissions(1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:id:2"},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user