Teams: Support team UIDs in APIs (#94011)
* support team UIDs in APIs * unify middleware logic and add team tests * add UID test to resource permissions * remove unused middleware
This commit is contained in:
@@ -55,34 +55,35 @@ func ProvideTeamAPI(
|
||||
|
||||
func (tapi *TeamAPI) registerRoutes(router routing.RouteRegister, ac accesscontrol.AccessControl) {
|
||||
authorize := accesscontrol.Middleware(ac)
|
||||
teamResolver := team.MiddlewareTeamUIDResolver(tapi.teamService, ":teamId")
|
||||
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,
|
||||
teamsRoute.Put("/:teamId", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsWrite,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.updateTeam))
|
||||
teamsRoute.Delete("/:teamId", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsDelete,
|
||||
teamsRoute.Delete("/:teamId", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsDelete,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.deleteTeamByID))
|
||||
teamsRoute.Get("/:teamId/members", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsRead,
|
||||
teamsRoute.Get("/:teamId/members", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsRead,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.getTeamMembers))
|
||||
teamsRoute.Post("/:teamId/members", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
|
||||
teamsRoute.Post("/:teamId/members", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.addTeamMember))
|
||||
teamsRoute.Put("/:teamId/members/:userId", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
|
||||
teamsRoute.Put("/:teamId/members/:userId", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.updateTeamMember))
|
||||
teamsRoute.Put("/:teamId/members", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
|
||||
teamsRoute.Put("/:teamId/members", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.setTeamMemberships))
|
||||
teamsRoute.Delete("/:teamId/members/:userId", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
|
||||
teamsRoute.Delete("/:teamId/members/:userId", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.removeTeamMember))
|
||||
teamsRoute.Get("/:teamId/preferences", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead,
|
||||
teamsRoute.Get("/:teamId/preferences", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.getTeamPreferences))
|
||||
teamsRoute.Put("/:teamId/preferences", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsWrite,
|
||||
teamsRoute.Put("/:teamId/preferences", teamResolver, 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,
|
||||
teamsRoute.Get("/:teamId", teamResolver, authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead,
|
||||
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.getTeamByID))
|
||||
teamsRoute.Get("/search", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead)),
|
||||
routing.Wrap(tapi.searchTeams))
|
||||
|
||||
@@ -28,14 +28,18 @@ import (
|
||||
"github.com/grafana/grafana/pkg/web/webtest"
|
||||
)
|
||||
|
||||
func SetupAPITestServer(t *testing.T, opts ...func(a *TeamAPI)) *webtest.Server {
|
||||
func SetupAPITestServer(t *testing.T, teamService team.Service, opts ...func(a *TeamAPI)) *webtest.Server {
|
||||
t.Helper()
|
||||
router := routing.NewRouteRegister()
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LDAPAuthEnabled = true
|
||||
|
||||
if teamService == nil {
|
||||
teamService = teamtest.NewFakeService()
|
||||
}
|
||||
|
||||
a := ProvideTeamAPI(router,
|
||||
teamtest.NewFakeService(),
|
||||
teamService,
|
||||
actest.FakeService{},
|
||||
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
||||
&actest.FakePermissionsService{},
|
||||
@@ -55,7 +59,7 @@ func SetupAPITestServer(t *testing.T, opts ...func(a *TeamAPI)) *webtest.Server
|
||||
}
|
||||
|
||||
func TestAddTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t)
|
||||
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||
|
||||
t.Run("should be able to add team member with correct permission", func(t *testing.T) {
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
@@ -68,6 +72,17 @@ func TestAddTeamMembersAPIEndpoint(t *testing.T) {
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("should be able to add team member with correct permission by UID", func(t *testing.T) {
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
server.NewRequest(http.MethodPost, "/api/teams/a00001/members", strings.NewReader("{\"userId\": 1}")),
|
||||
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.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}")),
|
||||
@@ -81,7 +96,7 @@ func TestAddTeamMembersAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t)
|
||||
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedIsMember: true, ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||
|
||||
t.Run("should be able to get team members with correct permission", func(t *testing.T) {
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
@@ -93,6 +108,18 @@ func TestGetTeamMembersAPIEndpoint(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("should be able to get team members with correct permission by UID", func(t *testing.T) {
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
server.NewGetRequest("/api/teams/a00001/members"),
|
||||
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.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"),
|
||||
@@ -106,9 +133,7 @@ func TestGetTeamMembersAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
||||
hs.teamService = &teamtest.FakeService{ExpectedIsMember: true}
|
||||
})
|
||||
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedIsMember: true, ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||
|
||||
t.Run("should be able to update team member with correct permission", func(t *testing.T) {
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
@@ -120,6 +145,18 @@ func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("should be able to update team member with correct permission by team UID", func(t *testing.T) {
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
server.NewRequest(http.MethodPut, "/api/teams/a00001/members/1", strings.NewReader("{\"permission\": 1}")),
|
||||
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.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}")),
|
||||
@@ -133,7 +170,7 @@ func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
||||
server := SetupAPITestServer(t, nil, func(hs *TeamAPI) {
|
||||
hs.teamService = &teamtest.FakeService{ExpectedIsMember: true}
|
||||
hs.teamPermissionsService = &actest.FakePermissionsService{}
|
||||
})
|
||||
|
||||
@@ -21,16 +21,14 @@ import (
|
||||
const (
|
||||
searchTeamsURL = "/api/teams/search"
|
||||
createTeamURL = "/api/teams/"
|
||||
detailTeamURL = "/api/teams/%d"
|
||||
detailTeamPreferenceURL = "/api/teams/%d/preferences"
|
||||
detailTeamURL = "/api/teams/%v"
|
||||
detailTeamPreferenceURL = "/api/teams/%v/preferences"
|
||||
teamCmd = `{"name": "MyTestTeam%d"}`
|
||||
teamPreferenceCmd = `{"theme": "dark"}`
|
||||
)
|
||||
|
||||
func TestTeamAPIEndpoint_CreateTeam(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
||||
hs.teamService = teamtest.NewFakeService()
|
||||
})
|
||||
server := SetupAPITestServer(t, nil)
|
||||
|
||||
input := strings.NewReader(fmt.Sprintf(teamCmd, 1))
|
||||
t.Run("Access control allows creating teams with the correct permissions", func(t *testing.T) {
|
||||
@@ -54,9 +52,7 @@ func TestTeamAPIEndpoint_CreateTeam(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTeamAPIEndpoint_SearchTeams(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
||||
hs.teamService = teamtest.NewFakeService()
|
||||
})
|
||||
server := SetupAPITestServer(t, nil)
|
||||
|
||||
t.Run("Access control prevents searching for teams with the incorrect permissions", func(t *testing.T) {
|
||||
req := server.NewGetRequest(searchTeamsURL)
|
||||
@@ -80,9 +76,7 @@ func TestTeamAPIEndpoint_SearchTeams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
||||
})
|
||||
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||
|
||||
url := fmt.Sprintf(detailTeamURL, 1)
|
||||
|
||||
@@ -106,6 +100,30 @@ func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control prevents getting a team when missing permissions by UID", func(t *testing.T) {
|
||||
url := fmt.Sprintf(detailTeamURL, "a00001")
|
||||
req := server.NewGetRequest(url)
|
||||
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
|
||||
}))
|
||||
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 by UID with the correct permissions", func(t *testing.T) {
|
||||
url := fmt.Sprintf(detailTeamURL, "a00001")
|
||||
req := server.NewGetRequest(url)
|
||||
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 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, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
|
||||
@@ -122,11 +140,9 @@ 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 *TeamAPI) {
|
||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
||||
})
|
||||
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||
|
||||
request := func(teamID int64, user *user.SignedInUser) (*http.Response, error) {
|
||||
request := func(teamID any, 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)
|
||||
@@ -141,6 +157,15 @@ func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control allows updating team by UID with the correct permissions", func(t *testing.T) {
|
||||
res, err := request("a00001", authedUserWithPermissions(1, 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, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:*"},
|
||||
@@ -164,11 +189,9 @@ 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 *TeamAPI) {
|
||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
||||
})
|
||||
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||
|
||||
request := func(teamID int64, user *user.SignedInUser) (*http.Response, error) {
|
||||
request := func(teamID any, 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)
|
||||
@@ -191,17 +214,26 @@ func TestTeamAPIEndpoint_DeleteTeam(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control allows deleting teams with the correct permissions by UID", func(t *testing.T) {
|
||||
res, err := request("a00001", authedUserWithPermissions(1, 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 *TeamAPI) {
|
||||
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}}, func(hs *TeamAPI) {
|
||||
hs.preferenceService = &preftest.FakePreferenceService{ExpectedPreference: &pref.Preference{}}
|
||||
})
|
||||
|
||||
request := func(teamID int64, user *user.SignedInUser) (*http.Response, error) {
|
||||
request := func(teamID any, user *user.SignedInUser) (*http.Response, error) {
|
||||
req := server.NewGetRequest(fmt.Sprintf(detailTeamPreferenceURL, teamID))
|
||||
req = webtest.RequestWithSignedInUser(req, user)
|
||||
return server.Send(req)
|
||||
@@ -216,6 +248,15 @@ func TestTeamAPIEndpoint_GetTeamPreferences(t *testing.T) {
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("Access control allows getting team preferences with the correct permissions by UID", func(t *testing.T) {
|
||||
res, err := request("a00001", authedUserWithPermissions(1, 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, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
|
||||
@@ -230,7 +271,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 *TeamAPI) {
|
||||
server := SetupAPITestServer(t, nil, func(hs *TeamAPI) {
|
||||
hs.preferenceService = &preftest.FakePreferenceService{ExpectedPreference: &pref.Preference{}}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user