mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Add userLogin filter to the permission search endpoint (#81137)
* RBAC: Search add user login filter * Switch to a userService resolving instead * Remove unused error * Fallback to use the cache * account for userID filter * Account for the error * snake case * Add test cases * Add api tests * Fix return on error * Re-order imports
This commit is contained in:
@@ -69,17 +69,31 @@ func (api *AccessControlAPI) getUserPermissions(c *contextmodel.ReqContext) resp
|
||||
return response.JSON(http.StatusOK, ac.GroupScopesByAction(permissions))
|
||||
}
|
||||
|
||||
// GET /api/access-control/users/permissions
|
||||
// GET /api/access-control/users/permissions/search
|
||||
func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext) response.Response {
|
||||
userIDString := c.Query("userId")
|
||||
userID, err := strconv.ParseInt(userIDString, 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "user ID is invalid", err)
|
||||
}
|
||||
searchOptions := ac.SearchOptions{
|
||||
UserLogin: c.Query("userLogin"),
|
||||
ActionPrefix: c.Query("actionPrefix"),
|
||||
Action: c.Query("action"),
|
||||
Scope: c.Query("scope"),
|
||||
}
|
||||
searchOptions.UserID = userID
|
||||
|
||||
// Validate inputs
|
||||
if (searchOptions.ActionPrefix != "") == (searchOptions.Action != "") {
|
||||
return response.JSON(http.StatusBadRequest, "provide one of 'action' or 'actionPrefix'")
|
||||
if (searchOptions.ActionPrefix != "") && (searchOptions.Action != "") {
|
||||
return response.JSON(http.StatusBadRequest, "'action' and 'actionPrefix' are mutually exclusive")
|
||||
}
|
||||
if (searchOptions.UserLogin != "") && (searchOptions.UserID > 0) {
|
||||
return response.JSON(http.StatusBadRequest, "'userId' and 'userLogin' are mutually exclusive")
|
||||
}
|
||||
if searchOptions.UserID <= 0 && searchOptions.UserLogin == "" &&
|
||||
searchOptions.ActionPrefix == "" && searchOptions.Action == "" {
|
||||
return response.JSON(http.StatusBadRequest, "at least one search option must be provided")
|
||||
}
|
||||
|
||||
// Compute metadata
|
||||
@@ -101,7 +115,7 @@ func (api *AccessControlAPI) searchUserPermissions(c *contextmodel.ReqContext) r
|
||||
userIDString := web.Params(c.Req)[":userID"]
|
||||
userID, err := strconv.ParseInt(userIDString, 10, 64)
|
||||
if err != nil {
|
||||
response.Error(http.StatusBadRequest, "user ID is invalid", err)
|
||||
return response.Error(http.StatusBadRequest, "user ID is invalid", err)
|
||||
}
|
||||
|
||||
searchOptions := ac.SearchOptions{
|
||||
|
||||
@@ -118,3 +118,77 @@ func TestAPI_getUserPermissions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessControlAPI_searchUsersPermissions(t *testing.T) {
|
||||
type testCase struct {
|
||||
desc string
|
||||
permissions map[int64][]ac.Permission
|
||||
filters string
|
||||
expectedOutput map[int64]map[string][]string
|
||||
expectedCode int
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
desc: "Should reject if no filter is provided",
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "Should reject if conflicting action filters are provided",
|
||||
filters: "?actionPrefix=grafana-test-app&action=grafana-test-app.projects:read",
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "Should reject if conflicting user filters are provided",
|
||||
filters: "?userLogin=admin&userId=2",
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "Should reject if invalid userID is provided",
|
||||
filters: "?userId=invalid",
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "Should work with valid filter provided",
|
||||
filters: "?userId=2",
|
||||
permissions: map[int64][]ac.Permission{2: {{Action: "users:read", Scope: "users:*"}}},
|
||||
expectedCode: http.StatusOK,
|
||||
expectedOutput: map[int64]map[string][]string{2: {"users:read": {"users:*"}}},
|
||||
},
|
||||
{
|
||||
desc: "Should reduce permissions",
|
||||
filters: "?userId=2",
|
||||
permissions: map[int64][]ac.Permission{2: {{Action: "users:read", Scope: "users:id:1"}, {Action: "users:read", Scope: "users:*"}}},
|
||||
expectedCode: http.StatusOK,
|
||||
expectedOutput: map[int64]map[string][]string{2: {"users:read": {"users:*"}}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
acSvc := actest.FakeService{ExpectedUsersPermissions: tt.permissions}
|
||||
accessControl := actest.FakeAccessControl{ExpectedEvaluate: true} // Always allow access to the endpoint
|
||||
api := NewAccessControlAPI(routing.NewRouteRegister(), accessControl, acSvc, featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall))
|
||||
api.RegisterAPIEndpoints()
|
||||
|
||||
server := webtest.NewServer(t, api.RouteRegister)
|
||||
url := "/api/access-control/users/permissions/search" + tt.filters
|
||||
|
||||
req := server.NewGetRequest(url)
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{},
|
||||
})
|
||||
res, err := server.Send(req)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedCode, res.StatusCode)
|
||||
|
||||
if tt.expectedCode == http.StatusOK {
|
||||
var output map[int64]map[string][]string
|
||||
err := json.NewDecoder(res.Body).Decode(&output)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedOutput, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user