refactoring dashoard folder guardian
This commit is contained in:
@@ -1,143 +0,0 @@
|
||||
package guardian
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// FilterRestrictedDashboards filters out dashboards from the list that the user does have access to
|
||||
func FilterRestrictedDashboards(dashList []int64, orgId int64, userId int64) ([]int64, error) {
|
||||
user, err := getUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.IsGrafanaAdmin || user.OrgRole == m.ROLE_ADMIN {
|
||||
return dashList, nil
|
||||
}
|
||||
|
||||
filteredList, err := getAllowedDashboards(dashList, orgId, userId)
|
||||
return filteredList, err
|
||||
}
|
||||
|
||||
// CanViewAcl determines if a user has permission to view a dashboard's ACL
|
||||
func CanViewAcl(dashboardId int64, role m.RoleType, isGrafanaAdmin bool, orgId int64, userId int64) (bool, error) {
|
||||
if role == m.ROLE_ADMIN || isGrafanaAdmin {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
filteredList, err := getAllowedDashboards([]int64{dashboardId}, orgId, userId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(filteredList) > 0 && filteredList[0] == dashboardId {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// CanDeleteFromAcl determines if a user has permission to delete from a dashboard's ACL
|
||||
func CanDeleteFromAcl(dashboardId int64, role m.RoleType, isGrafanaAdmin bool, orgId int64, userId int64) (bool, error) {
|
||||
if role == m.ROLE_ADMIN || isGrafanaAdmin {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
permissions, err := getDashboardPermissions(dashboardId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(permissions) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
minimumPermission := m.PERMISSION_EDIT
|
||||
return checkPermission(minimumPermission, permissions, userId)
|
||||
}
|
||||
|
||||
// CheckDashboardPermissions determines if a user has permission to view, edit or save a dashboard
|
||||
func CheckDashboardPermissions(dashboardId int64, role m.RoleType, isGrafanaAdmin bool, userId int64) (bool, bool, bool, error) {
|
||||
if role == m.ROLE_ADMIN || isGrafanaAdmin {
|
||||
return true, true, true, nil
|
||||
}
|
||||
|
||||
permissions, err := getDashboardPermissions(dashboardId)
|
||||
if err != nil {
|
||||
return false, false, false, err
|
||||
}
|
||||
|
||||
if len(permissions) == 0 {
|
||||
return false, false, false, nil
|
||||
}
|
||||
|
||||
minimumPermission := m.PERMISSION_VIEW
|
||||
canView, err := checkPermission(minimumPermission, permissions, userId)
|
||||
if err != nil {
|
||||
return false, false, false, err
|
||||
}
|
||||
|
||||
minimumPermission = m.PERMISSION_READ_ONLY_EDIT
|
||||
canEdit, err := checkPermission(minimumPermission, permissions, userId)
|
||||
if err != nil {
|
||||
return false, false, false, err
|
||||
}
|
||||
|
||||
minimumPermission = m.PERMISSION_EDIT
|
||||
canSave, err := checkPermission(minimumPermission, permissions, userId)
|
||||
if err != nil {
|
||||
return false, false, false, err
|
||||
}
|
||||
|
||||
return canView, canEdit, canSave, nil
|
||||
}
|
||||
|
||||
func checkPermission(minimumPermission m.PermissionType, permissions []*m.DashboardAclInfoDTO, userId int64) (bool, error) {
|
||||
userGroups, err := getUserGroupsByUser(userId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, p := range permissions {
|
||||
if p.UserId == userId && p.Permissions >= minimumPermission {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, ug := range userGroups {
|
||||
if ug.Id == p.UserGroupId && p.Permissions >= minimumPermission {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func getUser(userId int64) (*m.SignedInUser, error) {
|
||||
query := m.GetSignedInUserQuery{UserId: userId}
|
||||
err := bus.Dispatch(&query)
|
||||
|
||||
return query.Result, err
|
||||
}
|
||||
|
||||
func getAllowedDashboards(dashList []int64, orgId int64, userId int64) ([]int64, error) {
|
||||
query := m.GetAllowedDashboardsQuery{UserId: userId, OrgId: orgId, DashList: dashList}
|
||||
err := bus.Dispatch(&query)
|
||||
|
||||
return query.Result, err
|
||||
}
|
||||
|
||||
func getDashboardPermissions(dashboardId int64) ([]*m.DashboardAclInfoDTO, error) {
|
||||
query := m.GetDashboardPermissionsQuery{DashboardId: dashboardId}
|
||||
err := bus.Dispatch(&query)
|
||||
|
||||
return query.Result, err
|
||||
}
|
||||
|
||||
func getUserGroupsByUser(userId int64) ([]*m.UserGroup, error) {
|
||||
query := m.GetUserGroupsByUserQuery{UserId: userId}
|
||||
err := bus.Dispatch(&query)
|
||||
|
||||
return query.Result, err
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package guardian
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestGuardian(t *testing.T) {
|
||||
|
||||
Convey("Given a user with list of dashboards that they have access to", t, func() {
|
||||
hitList := []int64{1, 2}
|
||||
|
||||
var orgId int64 = 1
|
||||
var userId int64 = 1
|
||||
|
||||
Convey("And the user is a Grafana admin", func() {
|
||||
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
|
||||
query.Result = &m.SignedInUser{IsGrafanaAdmin: true}
|
||||
return nil
|
||||
})
|
||||
|
||||
filteredHitlist, err := FilterRestrictedDashboards(hitList, orgId, userId)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("should return all dashboards", func() {
|
||||
So(len(filteredHitlist), ShouldEqual, 2)
|
||||
So(filteredHitlist[0], ShouldEqual, 1)
|
||||
So(filteredHitlist[1], ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("And the user is an org admin", func() {
|
||||
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
|
||||
query.Result = &m.SignedInUser{IsGrafanaAdmin: false, OrgRole: m.ROLE_ADMIN}
|
||||
return nil
|
||||
})
|
||||
|
||||
filteredHitlist, err := FilterRestrictedDashboards(hitList, orgId, userId)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("should return all dashboards", func() {
|
||||
So(len(filteredHitlist), ShouldEqual, 2)
|
||||
So(filteredHitlist[0], ShouldEqual, 1)
|
||||
So(filteredHitlist[1], ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("And the user is an editor", func() {
|
||||
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
|
||||
query.Result = &m.SignedInUser{IsGrafanaAdmin: false, OrgRole: m.ROLE_EDITOR}
|
||||
return nil
|
||||
})
|
||||
bus.AddHandler("test2", func(query *m.GetAllowedDashboardsQuery) error {
|
||||
query.Result = []int64{1}
|
||||
return nil
|
||||
})
|
||||
|
||||
filteredHitlist, err := FilterRestrictedDashboards(hitList, orgId, userId)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("should return dashboard that editor has access to", func() {
|
||||
So(len(filteredHitlist), ShouldEqual, 1)
|
||||
So(filteredHitlist[0], ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package guardian
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
@@ -20,6 +22,7 @@ func NewDashboardGuardian(dash *m.Dashboard, user *m.SignedInUser) *DashboardGua
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanSave() (bool, error) {
|
||||
fmt.Printf("user %v, %v", g.user.OrgRole, g.user.HasRole(m.ROLE_EDITOR))
|
||||
if !g.dashboard.HasAcl {
|
||||
return g.user.HasRole(m.ROLE_EDITOR), nil
|
||||
}
|
||||
@@ -69,12 +72,18 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Returns dashboard acl
|
||||
func (g *DashboardGuardian) getAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
if g.acl != nil {
|
||||
return g.acl, nil
|
||||
}
|
||||
|
||||
query := m.GetDashboardPermissionsQuery{DashboardId: g.dashboard.Id}
|
||||
dashId := g.dashboard.Id
|
||||
if g.dashboard.ParentId != 0 {
|
||||
dashId = g.dashboard.ParentId
|
||||
}
|
||||
|
||||
query := m.GetDashboardPermissionsQuery{DashboardId: dashId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -76,11 +75,6 @@ func searchHandler(query *Query) error {
|
||||
hits = filtered
|
||||
}
|
||||
|
||||
hits, err := removeRestrictedDashboardsFromList(hits, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sort main result array
|
||||
sort.Sort(hits)
|
||||
|
||||
@@ -102,29 +96,6 @@ func searchHandler(query *Query) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeRestrictedDashboardsFromList(hits HitList, query *Query) (HitList, error) {
|
||||
var dashboardIds = []int64{}
|
||||
for _, hit := range hits {
|
||||
dashboardIds = append(dashboardIds, hit.Id)
|
||||
}
|
||||
|
||||
filteredHits, err := guardian.FilterRestrictedDashboards(dashboardIds, query.OrgId, query.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filtered := HitList{}
|
||||
for _, hit := range hits {
|
||||
for _, dashId := range filteredHits {
|
||||
if hit.Id == dashId {
|
||||
filtered = append(filtered, hit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func stringInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
|
||||
@@ -192,7 +192,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
|
||||
err = DeleteDashboard(&m.DeleteDashboardCommand{
|
||||
OrgId: 1,
|
||||
Slug: testDash.Slug,
|
||||
Id: testDash.Id,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -357,7 +357,7 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
|
||||
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId}
|
||||
dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
|
||||
has, err := sess.Get(&dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -23,7 +23,7 @@ func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
entity := m.DashboardAcl{
|
||||
Permissions: cmd.PermissionType,
|
||||
Permissions: cmd.Permissions,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
if _, err := sess.Cols("updated", "permissions").Where("dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId).Update(&entity); err != nil {
|
||||
@@ -40,7 +40,7 @@ func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
DashboardId: cmd.DashboardId,
|
||||
Permissions: cmd.PermissionType,
|
||||
Permissions: cmd.Permissions,
|
||||
}
|
||||
|
||||
cols := []string{"org_id", "created", "updated", "dashboard_id", "permissions"}
|
||||
@@ -64,6 +64,7 @@ func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand
|
||||
dashboard := m.Dashboard{
|
||||
HasAcl: true,
|
||||
}
|
||||
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR parent_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,19 +18,19 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
|
||||
Convey("When adding dashboard permission with userId and userGroupId set to 0", func() {
|
||||
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
|
||||
OrgId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
PermissionType: m.PERMISSION_EDIT,
|
||||
OrgId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldEqual, m.ErrDashboardPermissionUserOrUserGroupEmpty)
|
||||
})
|
||||
|
||||
Convey("Should be able to add dashboard permission", func() {
|
||||
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
PermissionType: m.PERMISSION_EDIT,
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -38,8 +38,8 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
err = GetDashboardPermissions(q1)
|
||||
So(err, ShouldBeNil)
|
||||
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q1.Result[0].PermissionType, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].Permissions, ShouldEqual, "Edit")
|
||||
So(q1.Result[0].Permissions, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].PermissionName, ShouldEqual, "Edit")
|
||||
So(q1.Result[0].UserId, ShouldEqual, currentUser.Id)
|
||||
So(q1.Result[0].UserLogin, ShouldEqual, currentUser.Login)
|
||||
So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email)
|
||||
@@ -54,10 +54,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
|
||||
Convey("Should be able to update an existing permission", func() {
|
||||
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
PermissionType: m.PERMISSION_READ_ONLY_EDIT,
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_READ_ONLY_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 1)
|
||||
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q3.Result[0].PermissionType, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT)
|
||||
So(q3.Result[0].Permissions, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT)
|
||||
So(q3.Result[0].UserId, ShouldEqual, 1)
|
||||
|
||||
})
|
||||
@@ -93,10 +93,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
|
||||
Convey("Should be able to add a user permission for a user group", func() {
|
||||
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
|
||||
OrgId: 1,
|
||||
UserGroupId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
PermissionType: m.PERMISSION_EDIT,
|
||||
OrgId: 1,
|
||||
UserGroupId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -104,16 +104,16 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
err = GetDashboardPermissions(q1)
|
||||
So(err, ShouldBeNil)
|
||||
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q1.Result[0].PermissionType, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].Permissions, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].UserGroupId, ShouldEqual, group1.Result.Id)
|
||||
})
|
||||
|
||||
Convey("Should be able to update an existing permission for a user group", func() {
|
||||
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
|
||||
OrgId: 1,
|
||||
UserGroupId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
PermissionType: m.PERMISSION_READ_ONLY_EDIT,
|
||||
OrgId: 1,
|
||||
UserGroupId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_READ_ONLY_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 1)
|
||||
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q3.Result[0].PermissionType, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT)
|
||||
So(q3.Result[0].Permissions, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT)
|
||||
So(q3.Result[0].UserGroupId, ShouldEqual, group1.Result.Id)
|
||||
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
@@ -69,12 +68,10 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to delete dashboard", func() {
|
||||
insertTestDashboard("delete me", 1, 0, false, "delete this")
|
||||
|
||||
dashboardSlug := slug.Make("delete me")
|
||||
dash := insertTestDashboard("delete me", 1, 0, false, "delete this")
|
||||
|
||||
err := DeleteDashboard(&m.DeleteDashboardCommand{
|
||||
Slug: dashboardSlug,
|
||||
Id: dash.Id,
|
||||
OrgId: 1,
|
||||
})
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@ func GetDashboardVersion(query *m.GetDashboardVersionQuery) error {
|
||||
|
||||
// GetDashboardVersions gets all dashboard versions for the given dashboard ID.
|
||||
func GetDashboardVersions(query *m.GetDashboardVersionsQuery) error {
|
||||
if query.Limit == 0 {
|
||||
query.Limit = 1000
|
||||
}
|
||||
|
||||
err := x.Table("dashboard_version").
|
||||
Select(`dashboard_version.id,
|
||||
dashboard_version.dashboard_id,
|
||||
|
||||
@@ -174,10 +174,10 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
|
||||
err = AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, PermissionType: m.PERMISSION_EDIT})
|
||||
err = AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permissions: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, PermissionType: m.PERMISSION_EDIT})
|
||||
err = AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permissions: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When org user is deleted", func() {
|
||||
|
||||
Reference in New Issue
Block a user