mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
413548806d
commit
acd13e05ef
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
|
"github.com/grafana/grafana/pkg/services/team"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
@ -42,21 +43,27 @@ func (a *api) registerEndpoints() {
|
|||||||
licenseMW = nopMiddleware
|
licenseMW = nopMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
|
teamUIDResolver := team.MiddlewareTeamUIDResolver(a.service.teamService, ":teamID")
|
||||||
|
teamUIDResolverResource := func() web.Handler { return func(c *contextmodel.ReqContext) {} }() // no-op
|
||||||
|
if a.service.options.Resource == "teams" {
|
||||||
|
teamUIDResolverResource = team.MiddlewareTeamUIDResolver(a.service.teamService, ":resourceID")
|
||||||
|
}
|
||||||
|
|
||||||
a.router.Group(fmt.Sprintf("/api/access-control/%s", a.service.options.Resource), func(r routing.RouteRegister) {
|
a.router.Group(fmt.Sprintf("/api/access-control/%s", a.service.options.Resource), func(r routing.RouteRegister) {
|
||||||
actionRead := fmt.Sprintf("%s.permissions:read", a.service.options.Resource)
|
actionRead := fmt.Sprintf("%s.permissions:read", a.service.options.Resource)
|
||||||
actionWrite := fmt.Sprintf("%s.permissions:write", a.service.options.Resource)
|
actionWrite := fmt.Sprintf("%s.permissions:write", a.service.options.Resource)
|
||||||
scope := accesscontrol.Scope(a.service.options.Resource, a.service.options.ResourceAttribute, accesscontrol.Parameter(":resourceID"))
|
scope := accesscontrol.Scope(a.service.options.Resource, a.service.options.ResourceAttribute, accesscontrol.Parameter(":resourceID"))
|
||||||
r.Get("/description", auth(accesscontrol.EvalPermission(actionRead)), routing.Wrap(a.getDescription))
|
r.Get("/description", auth(accesscontrol.EvalPermission(actionRead)), routing.Wrap(a.getDescription))
|
||||||
r.Get("/:resourceID", auth(accesscontrol.EvalPermission(actionRead, scope)), routing.Wrap(a.getPermissions))
|
r.Get("/:resourceID", teamUIDResolverResource, auth(accesscontrol.EvalPermission(actionRead, scope)), routing.Wrap(a.getPermissions))
|
||||||
r.Post("/:resourceID", licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setPermissions))
|
r.Post("/:resourceID", teamUIDResolverResource, licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setPermissions))
|
||||||
if a.service.options.Assignments.Users {
|
if a.service.options.Assignments.Users {
|
||||||
r.Post("/:resourceID/users/:userID", licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission))
|
r.Post("/:resourceID/users/:userID", licenseMW, teamUIDResolverResource, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission))
|
||||||
}
|
}
|
||||||
if a.service.options.Assignments.Teams {
|
if a.service.options.Assignments.Teams {
|
||||||
r.Post("/:resourceID/teams/:teamID", licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setTeamPermission))
|
r.Post("/:resourceID/teams/:teamID", licenseMW, teamUIDResolverResource, teamUIDResolver, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setTeamPermission))
|
||||||
}
|
}
|
||||||
if a.service.options.Assignments.BuiltInRoles {
|
if a.service.options.Assignments.BuiltInRoles {
|
||||||
r.Post("/:resourceID/builtInRoles/:builtInRole", licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setBuiltinRolePermission))
|
r.Post("/:resourceID/builtInRoles/:builtInRole", teamUIDResolverResource, licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setBuiltinRolePermission))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -257,6 +257,7 @@ type setTeamPermissionTestCase struct {
|
|||||||
expectedStatus int
|
expectedStatus int
|
||||||
permission string
|
permission string
|
||||||
permissions []accesscontrol.Permission
|
permissions []accesscontrol.Permission
|
||||||
|
byUID bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApi_setTeamPermission(t *testing.T) {
|
func TestApi_setTeamPermission(t *testing.T) {
|
||||||
@ -308,6 +309,20 @@ func TestApi_setTeamPermission(t *testing.T) {
|
|||||||
{Action: "dashboards.permissions:read", Scope: "dashboards:id:1"},
|
{Action: "dashboards.permissions:read", Scope: "dashboards:id:1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "should set View permission for team with id 1 but through UID",
|
||||||
|
teamID: 1,
|
||||||
|
resourceID: "1",
|
||||||
|
expectedStatus: 200,
|
||||||
|
permission: "View",
|
||||||
|
byUID: true,
|
||||||
|
permissions: []accesscontrol.Permission{
|
||||||
|
{Action: "dashboards.permissions:read", Scope: "dashboards:id:1"},
|
||||||
|
{Action: "dashboards.permissions:write", Scope: "dashboards:id:1"},
|
||||||
|
{Action: accesscontrol.ActionTeamsRead, Scope: accesscontrol.ScopeTeamsAll},
|
||||||
|
{Action: accesscontrol.ActionOrgUsersRead, Scope: accesscontrol.ScopeUsersAll},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -316,10 +331,16 @@ func TestApi_setTeamPermission(t *testing.T) {
|
|||||||
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service)
|
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service)
|
||||||
|
|
||||||
// seed team
|
// seed team
|
||||||
_, err := teamSvc.CreateTeam(context.Background(), "test", "test@test.com", 1)
|
team, err := teamSvc.CreateTeam(context.Background(), "test", "test@test.com", 1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "teams", strconv.Itoa(int(tt.teamID)))
|
assignTo := strconv.Itoa(int(tt.teamID))
|
||||||
|
if tt.byUID {
|
||||||
|
if team.ID == tt.teamID {
|
||||||
|
assignTo = team.UID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "teams", assignTo)
|
||||||
assert.Equal(t, tt.expectedStatus, recorder.Code)
|
assert.Equal(t, tt.expectedStatus, recorder.Code)
|
||||||
|
|
||||||
assert.Equal(t, tt.expectedStatus, recorder.Code)
|
assert.Equal(t, tt.expectedStatus, recorder.Code)
|
||||||
|
@ -54,8 +54,10 @@ type DeleteTeamCommand struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetTeamByIDQuery struct {
|
type GetTeamByIDQuery struct {
|
||||||
OrgID int64
|
OrgID int64
|
||||||
|
// Get team by ID or UID. If ID is set, UID is ignored.
|
||||||
ID int64
|
ID int64
|
||||||
|
UID string
|
||||||
SignedInUser identity.Requester
|
SignedInUser identity.Requester
|
||||||
HiddenUsers map[string]struct{}
|
HiddenUsers map[string]struct{}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,10 @@ package team
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
@ -18,3 +22,24 @@ type Service interface {
|
|||||||
GetTeamMembers(ctx context.Context, query *GetTeamMembersQuery) ([]*TeamMemberDTO, error)
|
GetTeamMembers(ctx context.Context, query *GetTeamMembersQuery) ([]*TeamMemberDTO, error)
|
||||||
RegisterDelete(query string)
|
RegisterDelete(query string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MiddlewareTeamUIDResolver(teamService Service, paramName string) web.Handler {
|
||||||
|
return func(c *contextmodel.ReqContext) {
|
||||||
|
// Get team id from request, fetch team and replace teamId with team id
|
||||||
|
teamID := web.Params(c.Req)[paramName]
|
||||||
|
// if teamID is empty or is an integer, we assume it's a team id and we don't need to resolve it
|
||||||
|
_, err := strconv.ParseInt(teamID, 10, 64)
|
||||||
|
if teamID == "" || err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
team, err := teamService.GetTeamByID(c.Req.Context(), &GetTeamByIDQuery{UID: teamID, OrgID: c.OrgID})
|
||||||
|
if err == nil {
|
||||||
|
gotParams := web.Params(c.Req)
|
||||||
|
gotParams[paramName] = strconv.FormatInt(team.ID, 10)
|
||||||
|
web.SetURLParams(c.Req, gotParams)
|
||||||
|
} else {
|
||||||
|
c.JsonApiErr(404, "Not found", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -55,34 +55,35 @@ func ProvideTeamAPI(
|
|||||||
|
|
||||||
func (tapi *TeamAPI) registerRoutes(router routing.RouteRegister, ac accesscontrol.AccessControl) {
|
func (tapi *TeamAPI) registerRoutes(router routing.RouteRegister, ac accesscontrol.AccessControl) {
|
||||||
authorize := accesscontrol.Middleware(ac)
|
authorize := accesscontrol.Middleware(ac)
|
||||||
|
teamResolver := team.MiddlewareTeamUIDResolver(tapi.teamService, ":teamId")
|
||||||
router.Group("/api", func(apiRoute routing.RouteRegister) {
|
router.Group("/api", func(apiRoute routing.RouteRegister) {
|
||||||
// team (admin permission required)
|
// team (admin permission required)
|
||||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
||||||
teamsRoute.Post("/", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsCreate)),
|
teamsRoute.Post("/", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsCreate)),
|
||||||
routing.Wrap(tapi.createTeam))
|
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))
|
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))
|
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))
|
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))
|
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))
|
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))
|
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))
|
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))
|
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))
|
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.updateTeamPreferences))
|
||||||
}, requestmeta.SetOwner(requestmeta.TeamAuth))
|
}, requestmeta.SetOwner(requestmeta.TeamAuth))
|
||||||
|
|
||||||
// team without requirement of user to be org admin
|
// team without requirement of user to be org admin
|
||||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
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))
|
accesscontrol.ScopeTeamsID)), routing.Wrap(tapi.getTeamByID))
|
||||||
teamsRoute.Get("/search", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead)),
|
teamsRoute.Get("/search", authorize(accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead)),
|
||||||
routing.Wrap(tapi.searchTeams))
|
routing.Wrap(tapi.searchTeams))
|
||||||
|
@ -28,14 +28,18 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/web/webtest"
|
"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()
|
t.Helper()
|
||||||
router := routing.NewRouteRegister()
|
router := routing.NewRouteRegister()
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
cfg.LDAPAuthEnabled = true
|
cfg.LDAPAuthEnabled = true
|
||||||
|
|
||||||
|
if teamService == nil {
|
||||||
|
teamService = teamtest.NewFakeService()
|
||||||
|
}
|
||||||
|
|
||||||
a := ProvideTeamAPI(router,
|
a := ProvideTeamAPI(router,
|
||||||
teamtest.NewFakeService(),
|
teamService,
|
||||||
actest.FakeService{},
|
actest.FakeService{},
|
||||||
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
||||||
&actest.FakePermissionsService{},
|
&actest.FakePermissionsService{},
|
||||||
@ -55,7 +59,7 @@ func SetupAPITestServer(t *testing.T, opts ...func(a *TeamAPI)) *webtest.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAddTeamMembersAPIEndpoint(t *testing.T) {
|
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) {
|
t.Run("should be able to add team member with correct permission", func(t *testing.T) {
|
||||||
req := webtest.RequestWithSignedInUser(
|
req := webtest.RequestWithSignedInUser(
|
||||||
@ -68,6 +72,17 @@ func TestAddTeamMembersAPIEndpoint(t *testing.T) {
|
|||||||
require.NoError(t, res.Body.Close())
|
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) {
|
t.Run("should not be able to add team member without correct permission", func(t *testing.T) {
|
||||||
req := webtest.RequestWithSignedInUser(
|
req := webtest.RequestWithSignedInUser(
|
||||||
server.NewRequest(http.MethodPost, "/api/teams/1/members", strings.NewReader("{\"userId\": 1}")),
|
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) {
|
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) {
|
t.Run("should be able to get team members with correct permission", func(t *testing.T) {
|
||||||
req := webtest.RequestWithSignedInUser(
|
req := webtest.RequestWithSignedInUser(
|
||||||
@ -93,6 +108,18 @@ func TestGetTeamMembersAPIEndpoint(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
require.NoError(t, res.Body.Close())
|
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) {
|
t.Run("should not be able to get team members without correct permission", func(t *testing.T) {
|
||||||
req := webtest.RequestWithSignedInUser(
|
req := webtest.RequestWithSignedInUser(
|
||||||
server.NewGetRequest("/api/teams/1/members"),
|
server.NewGetRequest("/api/teams/1/members"),
|
||||||
@ -106,9 +133,7 @@ func TestGetTeamMembersAPIEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
|
func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
|
||||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedIsMember: true, ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||||
hs.teamService = &teamtest.FakeService{ExpectedIsMember: true}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be able to update team member with correct permission", func(t *testing.T) {
|
t.Run("should be able to update team member with correct permission", func(t *testing.T) {
|
||||||
req := webtest.RequestWithSignedInUser(
|
req := webtest.RequestWithSignedInUser(
|
||||||
@ -120,6 +145,18 @@ func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
require.NoError(t, res.Body.Close())
|
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) {
|
t.Run("should not be able to update team member without correct permission", func(t *testing.T) {
|
||||||
req := webtest.RequestWithSignedInUser(
|
req := webtest.RequestWithSignedInUser(
|
||||||
server.NewRequest(http.MethodPut, "/api/teams/1/members/1", strings.NewReader("{\"permission\": 1}")),
|
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) {
|
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.teamService = &teamtest.FakeService{ExpectedIsMember: true}
|
||||||
hs.teamPermissionsService = &actest.FakePermissionsService{}
|
hs.teamPermissionsService = &actest.FakePermissionsService{}
|
||||||
})
|
})
|
||||||
|
@ -21,16 +21,14 @@ import (
|
|||||||
const (
|
const (
|
||||||
searchTeamsURL = "/api/teams/search"
|
searchTeamsURL = "/api/teams/search"
|
||||||
createTeamURL = "/api/teams/"
|
createTeamURL = "/api/teams/"
|
||||||
detailTeamURL = "/api/teams/%d"
|
detailTeamURL = "/api/teams/%v"
|
||||||
detailTeamPreferenceURL = "/api/teams/%d/preferences"
|
detailTeamPreferenceURL = "/api/teams/%v/preferences"
|
||||||
teamCmd = `{"name": "MyTestTeam%d"}`
|
teamCmd = `{"name": "MyTestTeam%d"}`
|
||||||
teamPreferenceCmd = `{"theme": "dark"}`
|
teamPreferenceCmd = `{"theme": "dark"}`
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTeamAPIEndpoint_CreateTeam(t *testing.T) {
|
func TestTeamAPIEndpoint_CreateTeam(t *testing.T) {
|
||||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
server := SetupAPITestServer(t, nil)
|
||||||
hs.teamService = teamtest.NewFakeService()
|
|
||||||
})
|
|
||||||
|
|
||||||
input := strings.NewReader(fmt.Sprintf(teamCmd, 1))
|
input := strings.NewReader(fmt.Sprintf(teamCmd, 1))
|
||||||
t.Run("Access control allows creating teams with the correct permissions", func(t *testing.T) {
|
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) {
|
func TestTeamAPIEndpoint_SearchTeams(t *testing.T) {
|
||||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
server := SetupAPITestServer(t, nil)
|
||||||
hs.teamService = teamtest.NewFakeService()
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Access control prevents searching for teams with the incorrect permissions", func(t *testing.T) {
|
t.Run("Access control prevents searching for teams with the incorrect permissions", func(t *testing.T) {
|
||||||
req := server.NewGetRequest(searchTeamsURL)
|
req := server.NewGetRequest(searchTeamsURL)
|
||||||
@ -80,9 +76,7 @@ func TestTeamAPIEndpoint_SearchTeams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
|
func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
|
||||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
|
||||||
})
|
|
||||||
|
|
||||||
url := fmt.Sprintf(detailTeamURL, 1)
|
url := fmt.Sprintf(detailTeamURL, 1)
|
||||||
|
|
||||||
@ -106,6 +100,30 @@ func TestTeamAPIEndpoint_GetTeamByID(t *testing.T) {
|
|||||||
require.NoError(t, res.Body.Close())
|
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) {
|
t.Run("Access control allows getting a team with wildcard scope", func(t *testing.T) {
|
||||||
req := server.NewGetRequest(url)
|
req := server.NewGetRequest(url)
|
||||||
req = webtest.RequestWithSignedInUser(req, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
|
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
|
// Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsWrite with teams:id:1 scope
|
||||||
// else return 403
|
// else return 403
|
||||||
func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
|
func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
|
||||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
|
||||||
})
|
|
||||||
|
|
||||||
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 := server.NewRequest(http.MethodPut, fmt.Sprintf(detailTeamURL, teamID), strings.NewReader(teamCmd))
|
||||||
req = webtest.RequestWithSignedInUser(req, user)
|
req = webtest.RequestWithSignedInUser(req, user)
|
||||||
return server.SendJSON(req)
|
return server.SendJSON(req)
|
||||||
@ -141,6 +157,15 @@ func TestTeamAPIEndpoint_UpdateTeam(t *testing.T) {
|
|||||||
require.NoError(t, res.Body.Close())
|
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) {
|
t.Run("Access control allows updating teams with the wildcard scope", func(t *testing.T) {
|
||||||
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
|
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
|
||||||
{Action: accesscontrol.ActionTeamsWrite, Scope: "teams:*"},
|
{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
|
// Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsDelete with teams:id:1 scope
|
||||||
// else return 403
|
// else return 403
|
||||||
func TestTeamAPIEndpoint_DeleteTeam(t *testing.T) {
|
func TestTeamAPIEndpoint_DeleteTeam(t *testing.T) {
|
||||||
server := SetupAPITestServer(t, func(hs *TeamAPI) {
|
server := SetupAPITestServer(t, &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001"}})
|
||||||
hs.teamService = &teamtest.FakeService{ExpectedTeamDTO: &team.TeamDTO{}}
|
|
||||||
})
|
|
||||||
|
|
||||||
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 := server.NewRequest(http.MethodDelete, fmt.Sprintf(detailTeamURL, teamID), http.NoBody)
|
||||||
req = webtest.RequestWithSignedInUser(req, user)
|
req = webtest.RequestWithSignedInUser(req, user)
|
||||||
return server.Send(req)
|
return server.Send(req)
|
||||||
@ -191,17 +214,26 @@ func TestTeamAPIEndpoint_DeleteTeam(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
require.NoError(t, res.Body.Close())
|
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,
|
// 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
|
// Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsRead with teams:id:1 scope
|
||||||
// else return 403
|
// else return 403
|
||||||
func TestTeamAPIEndpoint_GetTeamPreferences(t *testing.T) {
|
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{}}
|
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 := server.NewGetRequest(fmt.Sprintf(detailTeamPreferenceURL, teamID))
|
||||||
req = webtest.RequestWithSignedInUser(req, user)
|
req = webtest.RequestWithSignedInUser(req, user)
|
||||||
return server.Send(req)
|
return server.Send(req)
|
||||||
@ -216,6 +248,15 @@ func TestTeamAPIEndpoint_GetTeamPreferences(t *testing.T) {
|
|||||||
require.NoError(t, res.Body.Close())
|
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) {
|
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{
|
res, err := request(1, authedUserWithPermissions(1, 1, []accesscontrol.Permission{
|
||||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
|
{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
|
// Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsWrite with teams:id:1 scope
|
||||||
// else return 403
|
// else return 403
|
||||||
func TestTeamAPIEndpoint_UpdateTeamPreferences(t *testing.T) {
|
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{}}
|
hs.preferenceService = &preftest.FakePreferenceService{ExpectedPreference: &pref.Preference{}}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package teamimpl
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -268,6 +269,12 @@ func (ss *xormStore) Search(ctx context.Context, query *team.SearchTeamsQuery) (
|
|||||||
|
|
||||||
func (ss *xormStore) GetByID(ctx context.Context, query *team.GetTeamByIDQuery) (*team.TeamDTO, error) {
|
func (ss *xormStore) GetByID(ctx context.Context, query *team.GetTeamByIDQuery) (*team.TeamDTO, error) {
|
||||||
var queryResult *team.TeamDTO
|
var queryResult *team.TeamDTO
|
||||||
|
|
||||||
|
// Check if both ID and UID are unset
|
||||||
|
if query.ID == 0 && query.UID == "" {
|
||||||
|
return nil, errors.New("either ID or UID must be set")
|
||||||
|
}
|
||||||
|
|
||||||
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||||
var sql bytes.Buffer
|
var sql bytes.Buffer
|
||||||
params := make([]any, 0)
|
params := make([]any, 0)
|
||||||
@ -278,8 +285,14 @@ func (ss *xormStore) GetByID(ctx context.Context, query *team.GetTeamByIDQuery)
|
|||||||
params = append(params, user)
|
params = append(params, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
|
// Prioritize ID over UID
|
||||||
params = append(params, query.OrgID, query.ID)
|
if query.ID != 0 {
|
||||||
|
sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
|
||||||
|
params = append(params, query.OrgID, query.ID)
|
||||||
|
} else {
|
||||||
|
sql.WriteString(` WHERE team.org_id = ? and team.uid = ?`)
|
||||||
|
params = append(params, query.OrgID, query.UID)
|
||||||
|
}
|
||||||
|
|
||||||
var t team.TeamDTO
|
var t team.TeamDTO
|
||||||
exists, err := sess.SQL(sql.String(), params...).Get(&t)
|
exists, err := sess.SQL(sql.String(), params...).Get(&t)
|
||||||
|
@ -64,6 +64,7 @@ func (s *Service) GetTeamByID(ctx context.Context, query *team.GetTeamByIDQuery)
|
|||||||
ctx, span := s.tracer.Start(ctx, "team.GetTeamByID", trace.WithAttributes(
|
ctx, span := s.tracer.Start(ctx, "team.GetTeamByID", trace.WithAttributes(
|
||||||
attribute.Int64("orgID", query.OrgID),
|
attribute.Int64("orgID", query.OrgID),
|
||||||
attribute.Int64("teamID", query.ID),
|
attribute.Int64("teamID", query.ID),
|
||||||
|
attribute.String("teamUID", query.UID),
|
||||||
))
|
))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
return s.store.GetByID(ctx, query)
|
return s.store.GetByID(ctx, query)
|
||||||
|
@ -39,6 +39,7 @@ export interface State {
|
|||||||
// this is dummy data to pass to the table while the real data is loading
|
// this is dummy data to pass to the table while the real data is loading
|
||||||
const skeletonData: Team[] = new Array(3).fill(null).map((_, index) => ({
|
const skeletonData: Team[] = new Array(3).fill(null).map((_, index) => ({
|
||||||
id: index,
|
id: index,
|
||||||
|
uid: '',
|
||||||
memberCount: 0,
|
memberCount: 0,
|
||||||
name: '',
|
name: '',
|
||||||
orgId: 0,
|
orgId: 0,
|
||||||
|
@ -12,6 +12,7 @@ export const getMultipleMockTeams = (numberOfTeams: number): Team[] => {
|
|||||||
export const getMockTeam = (i = 1, overrides = {}): Team => {
|
export const getMockTeam = (i = 1, overrides = {}): Team => {
|
||||||
return {
|
return {
|
||||||
id: i,
|
id: i,
|
||||||
|
uid: '',
|
||||||
name: `test-${i}`,
|
name: `test-${i}`,
|
||||||
avatarUrl: 'some/url/',
|
avatarUrl: 'some/url/',
|
||||||
email: `test-${i}@test.com`,
|
email: `test-${i}@test.com`,
|
||||||
|
@ -9,6 +9,7 @@ import { AccessControlAction, Team, TeamPermissionLevel } from 'app/types';
|
|||||||
const loadingTeam = {
|
const loadingTeam = {
|
||||||
avatarUrl: 'public/img/user_profile.png',
|
avatarUrl: 'public/img/user_profile.png',
|
||||||
id: 1,
|
id: 1,
|
||||||
|
uid: '',
|
||||||
name: 'Loading',
|
name: 'Loading',
|
||||||
email: 'loading',
|
email: 'loading',
|
||||||
memberCount: 0,
|
memberCount: 0,
|
||||||
|
@ -15,6 +15,7 @@ export interface TeamDTO {
|
|||||||
// This is the team resource with permissions and metadata expanded
|
// This is the team resource with permissions and metadata expanded
|
||||||
export interface Team {
|
export interface Team {
|
||||||
id: number; // TODO switch to UUID
|
id: number; // TODO switch to UUID
|
||||||
|
uid: string; // Prefer UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AccessControl metadata associated with a given resource.
|
* AccessControl metadata associated with a given resource.
|
||||||
|
Loading…
Reference in New Issue
Block a user