Access control: Use ResolveIdentity() for authorizing in org (#85549)

* Access control: Use ResolveIdentity() for authorizing in org

* Fix tests

* Fix middleware tests

* Use ResolveIdentity in HasGlobalAccess() function

* remove makeTmpUser

* Cleanup

* Fix linter errors

* Fix test build

* Remove GetUserPermissionsInOrg()
This commit is contained in:
Alexander Zobnin
2024-04-10 12:42:13 +02:00
committed by GitHub
parent ebb4bb859e
commit 3127566a20
15 changed files with 296 additions and 419 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ func (hs *HTTPServer) registerRoutes() {
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
authorize := ac.Middleware(hs.AccessControl)
authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.accesscontrolService, hs.userService, hs.teamService)
authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.authnService)
quota := middleware.Quota(hs.QuotaService)
r := hs.RouteRegister
+31
View File
@@ -5,6 +5,9 @@ import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -136,6 +139,11 @@ func TestAPIEndpoint_UpdateOrg(t *testing.T) {
ExpectedSignedInUser: &user.SignedInUser{OrgID: tt.targetOrgID},
}
hs.accesscontrolService = actest.FakeService{}
hs.authnService = &authntest.FakeService{
ExpectedIdentity: &authn.Identity{
OrgID: tt.targetOrgID,
},
}
})
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, tt.path, strings.NewReader(tt.body)), userWithPermissions(1, tt.permission))
@@ -209,11 +217,22 @@ func TestAPIEndpoint_DeleteOrgs(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
expectedIdentity := &authn.Identity{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction(tt.permission),
},
}
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}}
hs.accesscontrolService = actest.FakeService{ExpectedPermissions: tt.permission}
hs.authnService = &authntest.FakeService{}
hs.authnService = &authntest.FakeService{
ExpectedIdentity: expectedIdentity,
}
})
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodDelete, "/api/orgs/1", nil), userWithPermissions(2, nil))
@@ -246,11 +265,23 @@ func TestAPIEndpoint_GetOrg(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
expectedIdentity := &authn.Identity{
ID: "user:1",
OrgID: 1,
Permissions: map[int64]map[string][]string{
0: accesscontrol.GroupScopesByAction(tt.permissions),
1: accesscontrol.GroupScopesByAction(tt.permissions),
},
}
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}}
hs.accesscontrolService = &actest.FakeService{ExpectedPermissions: tt.permissions}
hs.authnService = &authntest.FakeService{
ExpectedIdentity: expectedIdentity,
}
})
verify := func(path string) {
req := webtest.RequestWithSignedInUser(server.NewGetRequest(path), authedUserWithPermissions(1, 1, tt.permissions))
+1 -1
View File
@@ -539,7 +539,7 @@ func (hs *HTTPServer) hasPluginRequestedPermissions(c *contextmodel.ReqContext,
hs.log.Debug("check installer's permissions, plugin wants to register an external service")
evaluator := evalAllPermissions(plugin.JSONData.IAM.Permissions)
hasAccess := ac.HasGlobalAccess(hs.AccessControl, hs.accesscontrolService, c)
hasAccess := ac.HasGlobalAccess(hs.AccessControl, hs.authnService, c)
if hs.Cfg.RBACSingleOrganization {
// In a single organization setup, no need for a global check
hasAccess = ac.HasAccess(hs.AccessControl, c)
+21 -1
View File
@@ -12,12 +12,12 @@ import (
"strings"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/log/logtest"
@@ -32,6 +32,8 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest"
@@ -94,6 +96,16 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
ID: pluginID,
},
})
expectedIdentity := &authn.Identity{
OrgID: tc.permissionOrg,
Permissions: map[int64]map[string][]string{},
OrgRoles: map[int64]org.RoleType{},
}
expectedIdentity.Permissions[tc.permissionOrg] = ac.GroupScopesByAction(tc.permissions)
hs.authnService = &authntest.FakeService{
ExpectedIdentity: expectedIdentity,
}
})
t.Run(testName("Install", tc), func(t *testing.T) {
@@ -734,6 +746,14 @@ func TestHTTPServer_hasPluginRequestedPermissions(t *testing.T) {
hs.accesscontrolService = actest.FakeService{}
hs.AccessControl = acimpl.ProvideAccessControl(hs.Cfg)
expectedIdentity := &authn.Identity{
OrgID: tt.orgID,
Permissions: tt.permissions,
}
hs.authnService = &authntest.FakeService{
ExpectedIdentity: expectedIdentity,
}
c := &contextmodel.ReqContext{
Context: &web.Context{Req: httpReq},
SignedInUser: &user.SignedInUser{OrgID: tt.orgID, Permissions: tt.permissions},
+83 -49
View File
@@ -6,6 +6,9 @@ import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -67,6 +70,9 @@ func TestAPIEndpoint_GetOrgQuotas(t *testing.T) {
hs.userService = &usertest.FakeUserService{
ExpectedSignedInUser: &user.SignedInUser{OrgID: 2},
}
hs.authnService = &authntest.FakeService{
ExpectedIdentity: &authn.Identity{OrgID: 1},
}
})
t.Run("AccessControl allows viewing another org quotas with correct permissions", func(t *testing.T) {
@@ -96,60 +102,88 @@ func TestAPIEndpoint_GetOrgQuotas(t *testing.T) {
}
func TestAPIEndpoint_PutOrgQuotas(t *testing.T) {
cfg := setting.NewCfg()
cfg.Quota = setting.QuotaSettings{
Enabled: true,
Global: setting.GlobalQuota{
Org: 5,
type testCase struct {
desc string
userOrg int64
targetOrg int64
permissions map[int64][]accesscontrol.Permission
expectedCode int
}
tests := []testCase{
{
desc: "AccessControl allows updating another org quotas with correct permissions",
userOrg: 1,
targetOrg: 2,
permissions: map[int64][]accesscontrol.Permission{2: {{Action: accesscontrol.ActionOrgsQuotasWrite}}},
expectedCode: http.StatusOK,
},
Org: setting.OrgQuota{
User: 5,
{
desc: "AccessControl prevents updating another org quotas with correct permissions in another org",
userOrg: 1,
targetOrg: 2,
permissions: map[int64][]accesscontrol.Permission{1: {{Action: accesscontrol.ActionOrgsQuotasWrite}}},
expectedCode: http.StatusForbidden,
},
User: setting.UserQuota{
Org: 5,
{
desc: "AccessControl prevents updating another org quotas with incorrect permissions",
userOrg: 2,
targetOrg: 2,
permissions: map[int64][]accesscontrol.Permission{2: {{Action: "orgs:invalid"}}},
expectedCode: http.StatusForbidden,
},
}
fakeACService := &actest.FakeService{}
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = cfg
hs.accesscontrolService = fakeACService
hs.userService = &usertest.FakeUserService{
ExpectedSignedInUser: &user.SignedInUser{OrgID: 2},
}
})
input := strings.NewReader(testUpdateOrgQuotaCmd)
t.Run("AccessControl allows updating another org quotas with correct permissions", func(t *testing.T) {
user := userWithPermissions(2, []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}})
user.OrgID = 1
fakeACService.ExpectedPermissions = []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}}
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user)
response, err := server.SendJSON(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, response.StatusCode)
require.NoError(t, response.Body.Close())
})
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.Quota = setting.QuotaSettings{
Enabled: true,
Global: setting.GlobalQuota{
Org: 5,
},
Org: setting.OrgQuota{
User: 5,
},
User: setting.UserQuota{
Org: 5,
},
}
fakeACService := &actest.FakeService{}
input := strings.NewReader(testUpdateOrgQuotaCmd)
expectedIdentity := &authn.Identity{
OrgID: tt.userOrg,
Permissions: map[int64]map[string][]string{},
}
for orgID, permissions := range tt.permissions {
expectedIdentity.Permissions[orgID] = accesscontrol.GroupScopesByAction(permissions)
}
input = strings.NewReader(testUpdateOrgQuotaCmd)
t.Run("AccessControl prevents updating another org quotas with correct permissions in another org", func(t *testing.T) {
user := userWithPermissions(1, []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}})
user.Permissions[2] = nil
fakeACService.ExpectedPermissions = []accesscontrol.Permission{}
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user)
response, err := server.SendJSON(req)
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, response.StatusCode)
require.NoError(t, response.Body.Close())
})
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = cfg
hs.accesscontrolService = fakeACService
hs.userService = &usertest.FakeUserService{
ExpectedSignedInUser: &user.SignedInUser{OrgID: tt.userOrg},
}
hs.authnService = &authntest.FakeService{
ExpectedIdentity: expectedIdentity,
}
})
input = strings.NewReader(testUpdateOrgQuotaCmd)
t.Run("AccessControl prevents updating another org quotas with incorrect permissions", func(t *testing.T) {
user := userWithPermissions(2, []accesscontrol.Permission{{Action: "orgs:invalid"}})
fakeACService.ExpectedPermissions = []accesscontrol.Permission{}
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user)
response, err := server.SendJSON(req)
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, response.StatusCode)
require.NoError(t, response.Body.Close())
})
user := userWithPermissions(tt.userOrg, getFirstOrgPermissions(tt.permissions))
fakeACService.ExpectedPermissions = []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}}
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, tt.targetOrg, "org_user"), input), user)
response, err := server.SendJSON(req)
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, response.StatusCode)
require.NoError(t, response.Body.Close())
})
}
}
func getFirstOrgPermissions(p map[int64][]accesscontrol.Permission) []accesscontrol.Permission {
for _, permissions := range p {
return permissions
}
return []accesscontrol.Permission{}
}