Merge pull request #9788 from rhafer/filter-usertype
feat(graph): add support for filtering users by type
This commit is contained in:
@@ -237,7 +237,21 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctxHasFullPerms && (odataReq.Query.Filter != nil || odataReq.Query.Apply != nil || odataReq.Query.Expand != nil || odataReq.Query.Compute != nil) {
|
||||
if !ctxHasFullPerms && odataReq.Query.Filter != nil {
|
||||
// regular users are allowed to filter only by userType
|
||||
filter := odataReq.Query.Filter
|
||||
switch {
|
||||
case filter.Tree.Token.Type != godata.ExpressionTokenLogical:
|
||||
fallthrough
|
||||
case filter.Tree.Token.Value != "eq":
|
||||
fallthrough
|
||||
case filter.Tree.Children[0].Token.Value != "userType":
|
||||
logger.Debug().Interface("query", r.URL.Query()).Msg("forbidden filter for a regular user")
|
||||
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "filter has forbidden elements for regular users")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !ctxHasFullPerms && (odataReq.Query.Apply != nil || odataReq.Query.Expand != nil || odataReq.Query.Compute != nil) {
|
||||
// regular users can't use filter, apply, expand and compute
|
||||
logger.Debug().Interface("query", r.URL.Query()).Msg("forbidden query elements for a regular user")
|
||||
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "query has forbidden elements for regular users")
|
||||
@@ -254,34 +268,6 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
users, err = g.identityBackend.GetUsers(r.Context(), odataReq)
|
||||
}
|
||||
|
||||
if g.config.IncludeOCMSharees {
|
||||
gwc, err := g.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
term, err := identity.GetSearchValues(odataReq.Query)
|
||||
if err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
remoteUsersRes, err := gwc.FindAcceptedUsers(r.Context(), &invitepb.FindAcceptedUsersRequest{Filter: term})
|
||||
if err != nil {
|
||||
// TODO grpc FindAcceptedUsers call failed
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if remoteUsersRes.Status.Code != cs3rpc.Code_CODE_OK {
|
||||
// TODO "error searching remote users"
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, remoteUsersRes.Status.Message)
|
||||
return
|
||||
}
|
||||
for _, user := range remoteUsersRes.GetAcceptedUsers() {
|
||||
users = append(users, identity.CreateUserModelFromCS3(user))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users from backend")
|
||||
var errcode errorcode.Error
|
||||
@@ -1014,3 +1000,38 @@ func isValidUserType(userType string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (g Graph) searchOCMAcceptedUsers(ctx context.Context, odataReq *godata.GoDataRequest) ([]*libregraph.User, error) {
|
||||
logger := g.logger.SubloggerWithRequestID(ctx)
|
||||
users := []*libregraph.User{}
|
||||
|
||||
if !g.config.IncludeOCMSharees {
|
||||
logger.Debug().Msg("OCM sharing is disabled")
|
||||
return users, nil
|
||||
}
|
||||
|
||||
gwc, err := g.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
term, err := identity.GetSearchValues(odataReq.Query)
|
||||
if err != nil {
|
||||
return nil, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
|
||||
remoteUsersRes, err := gwc.FindAcceptedUsers(ctx, &invitepb.FindAcceptedUsersRequest{Filter: term})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("FindAcceptedUsers call failed")
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
if remoteUsersRes.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
|
||||
return nil, errorcode.FromCS3Status(remoteUsersRes.Status, nil)
|
||||
}
|
||||
|
||||
cs3Users := remoteUsersRes.GetAcceptedUsers()
|
||||
users = make([]*libregraph.User, 0, len(cs3Users))
|
||||
for _, user := range cs3Users {
|
||||
users = append(users, identity.CreateUserModelFromCS3(user))
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
@@ -124,19 +124,19 @@ func (g Graph) applyFilterLogical(ctx context.Context, req *godata.GoDataRequest
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
|
||||
// As we currently don't suppport the 'has' or 'in' operator, all our
|
||||
// currently supported user filters of the ExpressionTokenLogical type
|
||||
// require exactly two operands.
|
||||
if len(root.Children) != 2 {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
switch root.Token.Value {
|
||||
case "and":
|
||||
// 'and' needs 2 operands
|
||||
if len(root.Children) != 2 {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
return g.applyFilterLogicalAnd(ctx, req, root.Children[0], root.Children[1])
|
||||
case "or":
|
||||
// 'or' needs 2 operands
|
||||
if len(root.Children) != 2 {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
return g.applyFilterLogicalOr(ctx, req, root.Children[0], root.Children[1])
|
||||
case "eq":
|
||||
return g.applyFilterEq(ctx, req, root.Children[0], root.Children[1])
|
||||
}
|
||||
logger.Debug().Str("Token", root.Token.Value).Msg("unsupported logical filter")
|
||||
return users, unsupportedFilterError()
|
||||
@@ -222,6 +222,29 @@ func (g Graph) applyFilterLogicalOr(ctx context.Context, req *godata.GoDataReque
|
||||
}
|
||||
return filteredUsers, nil
|
||||
}
|
||||
|
||||
func (g Graph) applyFilterEq(ctx context.Context, req *godata.GoDataRequest, operand1 *godata.ParseNode, operand2 *godata.ParseNode) (users []*libregraph.User, err error) {
|
||||
// We only support the 'eq' on 'userType' for now
|
||||
switch {
|
||||
case operand1.Token.Type != godata.ExpressionTokenLiteral:
|
||||
fallthrough
|
||||
case operand1.Token.Value != "userType":
|
||||
fallthrough
|
||||
case operand2.Token.Type != godata.ExpressionTokenString:
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
|
||||
// unquote
|
||||
value := strings.Trim(operand2.Token.Value, "'")
|
||||
switch value {
|
||||
case "Member", "Guest":
|
||||
return g.identityBackend.GetUsers(ctx, req)
|
||||
case "Federated":
|
||||
return g.searchOCMAcceptedUsers(ctx, req)
|
||||
}
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
|
||||
func (g Graph) applyFilterLambda(ctx context.Context, req *godata.GoDataRequest, nodes []*godata.ParseNode) (users []*libregraph.User, err error) {
|
||||
logger := g.logger.SubloggerWithRequestID(ctx)
|
||||
if len(nodes) != 2 {
|
||||
|
||||
@@ -291,6 +291,14 @@ var _ = Describe("Users", func() {
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusForbidden))
|
||||
})
|
||||
It("denies filters other that 'userType eq ...' for unprivileged users", func() {
|
||||
permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$search=%22abc%22&$filter="+url.QueryEscape("memberOf/any(n:n/id eq 25cb7bc0-3168-4a0c-adbe-396f478ad494)"), nil)
|
||||
svc.GetUsers(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusForbidden))
|
||||
})
|
||||
|
||||
It("only returns a restricted set of attributes for unprivileged users", func() {
|
||||
permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
|
||||
user := &libregraph.User{}
|
||||
@@ -503,8 +511,8 @@ var _ = Describe("Users", func() {
|
||||
"appRoleAssignments/all(n:n/appRoleId eq 'id') and appRoleAssignments/any(n:n/appRoleId eq 'id')", http.StatusNotImplemented),
|
||||
Entry("with unsupported appRoleAssignments lambda filter operation",
|
||||
"appRoleAssignments/any(n:n/appRoleId ne 'id') and appRoleAssignments/any(n:n/appRoleId eq 'id')", http.StatusNotImplemented),
|
||||
Entry("with unsupported appRoleAssignments lambda filter operation",
|
||||
"appRoleAssignments/any(n:n/appRoleId eq 1) and appRoleAssignments/any(n:n/appRoleId eq 'id')", http.StatusNotImplemented),
|
||||
Entry("with unsupported userType in filter", "userType eq 'unsupported'", http.StatusNotImplemented),
|
||||
Entry("with unsupported property in eq filter", "unsupported eq 'unsupported'", http.StatusNotImplemented),
|
||||
)
|
||||
|
||||
DescribeTable("With a valid filter",
|
||||
@@ -1125,7 +1133,28 @@ var _ = Describe("Users", func() {
|
||||
)
|
||||
})
|
||||
Describe("GetUsers", func() {
|
||||
It("lists the users", func() {
|
||||
It("does not list the federated users without a filter", func() {
|
||||
gatewayClient.AssertNotCalled(GinkgoT(), "FindAcceptedUsers, mock.Anything, mock.Anything")
|
||||
permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
|
||||
|
||||
identityBackend.On("GetUsers", mock.Anything, mock.Anything, mock.Anything).Return(
|
||||
[]*libregraph.User{}, nil,
|
||||
)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$search=user", nil)
|
||||
svc.GetUsers(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
res := userList{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(res.Value)).To(Equal(0))
|
||||
})
|
||||
It("does list federated users when the right filter is used", func() {
|
||||
gatewayClient.On("FindAcceptedUsers", mock.Anything, mock.Anything).Return(&invitepb.FindAcceptedUsersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
AcceptedUsers: []*userv1beta1.User{
|
||||
@@ -1138,18 +1167,13 @@ var _ = Describe("Users", func() {
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
|
||||
Permission: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_UNKNOWN,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
}, nil)
|
||||
permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
|
||||
|
||||
identityBackend.On("GetUsers", mock.Anything, mock.Anything, mock.Anything).Return(
|
||||
[]*libregraph.User{}, nil,
|
||||
)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users", nil)
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$search=federated&$filter="+url.QueryEscape("userType eq 'Federated'"), nil)
|
||||
svc.GetUsers(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
@@ -1164,6 +1188,33 @@ var _ = Describe("Users", func() {
|
||||
Expect(res.Value[0].GetId()).To(Equal("federated"))
|
||||
Expect(res.Value[0].GetUserType()).To(Equal("Federated"))
|
||||
})
|
||||
It("does not list federated users when filtering for 'Member' users", func() {
|
||||
permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
|
||||
|
||||
user := &libregraph.User{}
|
||||
user.SetId("user1")
|
||||
user.SetUserType("Member")
|
||||
users := []*libregraph.User{user}
|
||||
|
||||
identityBackend.On("GetUsers", mock.Anything, mock.Anything, mock.Anything).Return(
|
||||
users, nil,
|
||||
)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$search=user&$filter="+url.QueryEscape("userType eq 'Member'"), nil)
|
||||
svc.GetUsers(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
res := userList{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(res.Value)).To(Equal(1))
|
||||
Expect(res.Value[0].GetId()).To(Equal("user1"))
|
||||
Expect(res.Value[0].GetUserType()).To(Equal("Member"))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user