Add support for $filter on appRoleAssignment
This add support for filtering on the `appRoleAssignment` relation of users. E.g. ``` $filter=appRoleAssignments/any(m:m/appRoleId eq '262982c1-2362-4afa-bfdf-8cbfef64a06e') ``` combining it with a filter on groupMemberShip does also work: ``` $filter=memberOf/any(m:m/id eq '262982c1-2362-4afa-bfdf-8cbfef64a06e') and appRoleAssignments/any(m:m/appRoleId eq 'd7beeea8-8ff4-406b-8fb6-ab2dd81e6b11') ``` The filter is still very inefficient as it always needs to get the full users list. We need to adapt it to only filter on a subset of users when using this filter 'and' combined with other filters. Closes: #5488
This commit is contained in:
committed by
Ralf Haferkamp
parent
db6dc5f8af
commit
1552f6df5a
@@ -81,16 +81,22 @@ func (g Graph) applyFilterLambda(ctx context.Context, req *godata.GoDataRequest,
|
||||
if len(nodes) != 2 {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
// We only support memberOf/any queries for now
|
||||
if nodes[0].Token.Type != godata.ExpressionTokenLiteral || nodes[0].Token.Value != "memberOf" {
|
||||
logger.Debug().Str("Token", nodes[0].Token.Value).Msg("unsupported relation for lambda filter")
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
// We only support the "any" operator for lambda queries for now
|
||||
if nodes[1].Token.Type != godata.ExpressionTokenLambda || nodes[1].Token.Value != "any" {
|
||||
logger.Debug().Str("Token", nodes[1].Token.Value).Msg("unsupported lambda filter")
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
return g.applyLambdaMemberOfAny(ctx, req, nodes[1].Children)
|
||||
if nodes[0].Token.Type != godata.ExpressionTokenLiteral {
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
switch nodes[0].Token.Value {
|
||||
case "memberOf":
|
||||
return g.applyLambdaMemberOfAny(ctx, req, nodes[1].Children)
|
||||
case "appRoleAssignments":
|
||||
return g.applyLambdaAppRoleAssignmentAny(ctx, req, nodes[1].Children)
|
||||
}
|
||||
logger.Debug().Str("Token", nodes[0].Token.Value).Msg("unsupported relation for lambda filter")
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
|
||||
func (g Graph) applyLambdaMemberOfAny(ctx context.Context, req *godata.GoDataRequest, nodes []*godata.ParseNode) (users []*libregraph.User, err error) {
|
||||
@@ -141,7 +147,85 @@ func (g Graph) applyMemberOfEq(ctx context.Context, req *godata.GoDataRequest, n
|
||||
default:
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
}
|
||||
|
||||
func (g Graph) applyLambdaAppRoleAssignmentAny(ctx context.Context, req *godata.GoDataRequest, nodes []*godata.ParseNode) (users []*libregraph.User, err error) {
|
||||
if len(nodes) != 2 {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
|
||||
// First element is the "name" of the lambda function's parameter
|
||||
if nodes[0].Token.Type != godata.ExpressionTokenLiteral {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
|
||||
// We only support the 'eq' expression for now
|
||||
if nodes[1].Token.Type != godata.ExpressionTokenLogical || nodes[1].Token.Value != "eq" {
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
return g.applyAppRoleAssignmentEq(ctx, req, nodes[1].Children)
|
||||
}
|
||||
|
||||
func (g Graph) applyAppRoleAssignmentEq(ctx context.Context, req *godata.GoDataRequest, nodes []*godata.ParseNode) (users []*libregraph.User, err error) {
|
||||
logger := g.logger.SubloggerWithRequestID(ctx)
|
||||
if len(nodes) != 2 {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
|
||||
if nodes[0].Token.Type != godata.ExpressionTokenNav {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
|
||||
if len(nodes[0].Children) != 2 {
|
||||
return users, invalidFilterError()
|
||||
}
|
||||
|
||||
if nodes[0].Children[1].Token.Value == "appRoleId" {
|
||||
var filterValue string
|
||||
switch nodes[1].Token.Type {
|
||||
case godata.ExpressionTokenGuid:
|
||||
filterValue = nodes[1].Token.Value
|
||||
case godata.ExpressionTokenString:
|
||||
// unquote
|
||||
filterValue = strings.Trim(nodes[1].Token.Value, "'")
|
||||
default:
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
|
||||
logger.Debug().Str("property", nodes[0].Children[1].Token.Value).Str("value", filterValue).Msg("Filtering appRoleAssignments by appRoleId")
|
||||
if users, err = g.identityBackend.GetUsers(ctx, req); err != nil {
|
||||
return users, err
|
||||
}
|
||||
|
||||
return g.filterUsersByAppRoleId(ctx, filterValue, users)
|
||||
}
|
||||
return users, unsupportedFilterError()
|
||||
}
|
||||
|
||||
func (g Graph) filterUsersByAppRoleId(ctx context.Context, appRoleId string, users []*libregraph.User) ([]*libregraph.User, error) {
|
||||
// We're using a map for the results here, in order to avoid returning
|
||||
// a user twice. The settings API, still has an issue that causes it to
|
||||
// duplicate some assignments on restart:
|
||||
// https://github.com/owncloud/ocis/issues/3432
|
||||
resultUsersMap := make(map[string]*libregraph.User, len(users))
|
||||
for _, user := range users {
|
||||
assignments, err := g.fetchAppRoleAssignments(ctx, user.GetId())
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
for _, assignment := range assignments {
|
||||
if assignment.GetAppRoleId() == appRoleId {
|
||||
if _, ok := resultUsersMap[user.GetId()]; !ok {
|
||||
resultUsersMap[user.GetId()] = user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resultUsers := make([]*libregraph.User, 0, len(resultUsersMap))
|
||||
for _, user := range resultUsersMap {
|
||||
resultUsers = append(resultUsers, user)
|
||||
}
|
||||
return resultUsers, nil
|
||||
}
|
||||
|
||||
func userSliceToMap(users []*libregraph.User) map[string]*libregraph.User {
|
||||
|
||||
@@ -340,16 +340,28 @@ var _ = Describe("Users", func() {
|
||||
Entry("with unsupported lambda token ", "memberOf/all(n:n/id eq 1)", http.StatusNotImplemented),
|
||||
Entry("with unsupported filter operation ", "memberOf/any(n:n/id ne 1)", http.StatusNotImplemented),
|
||||
Entry("with unsupported filter operand type", "memberOf/any(n:n/id eq 1)", http.StatusNotImplemented),
|
||||
Entry("with unsupported lambda filter property", "memberOf/any(n:n/name eq 'name')", http.StatusNotImplemented),
|
||||
Entry("with unsupported memberOf lambda filter property", "memberOf/any(n:n/name eq 'name')", http.StatusNotImplemented),
|
||||
Entry("with unsupported appRoleAssignments lambda filter property", "appRoleAssignments/any(n:n/id eq 'id')", http.StatusNotImplemented),
|
||||
)
|
||||
|
||||
DescribeTable("With a valid memberOf filter",
|
||||
DescribeTable("With a valid filter",
|
||||
func(filter string, status int) {
|
||||
user := &libregraph.User{}
|
||||
user.SetId("25cb7bc0-3168-4a0c-adbe-396f478ad494")
|
||||
users := []*libregraph.User{user}
|
||||
identityBackend.On("GetGroupMembers", mock.Anything, "25cb7bc0-3168-4a0c-adbe-396f478ad494", mock.Anything).Return(users, nil)
|
||||
identityBackend.On("GetGroupMembers", mock.Anything, "2713f1d5-6822-42bd-ad56-9f6c55a3a8fa", mock.Anything).Return([]*libregraph.User{}, nil)
|
||||
identityBackend.On("GetUsers", mock.Anything, mock.Anything).Return([]*libregraph.User{user}, nil)
|
||||
roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(func(ctx context.Context, in *settings.ListRoleAssignmentsRequest, opts ...client.CallOption) *settings.ListRoleAssignmentsResponse {
|
||||
return &settings.ListRoleAssignmentsResponse{Assignments: []*settingsmsg.UserRoleAssignment{
|
||||
{
|
||||
Id: "some-appRoleAssignment-ID",
|
||||
AccountUuid: user.GetId(),
|
||||
RoleId: "some-appRole-ID",
|
||||
},
|
||||
}}
|
||||
}, nil)
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$filter="+url.QueryEscape(filter), nil)
|
||||
svc.GetUsers(rr, r)
|
||||
|
||||
@@ -357,6 +369,7 @@ var _ = Describe("Users", func() {
|
||||
},
|
||||
Entry("with memberOf lambda filter with UUID", "memberOf/any(n:n/id eq 25cb7bc0-3168-4a0c-adbe-396f478ad494)", http.StatusOK),
|
||||
Entry("with memberOf lambda filter with UUID string", "memberOf/any(n:n/id eq '25cb7bc0-3168-4a0c-adbe-396f478ad494')", http.StatusOK),
|
||||
Entry("with appRoleAssignments lambda filter with appRoleId", "appRoleAssignments/any(n:n/appRoleId eq 'some-appRole-ID')", http.StatusOK),
|
||||
Entry("with two memberOf lambda filters",
|
||||
"memberOf/any(n:n/id eq 25cb7bc0-3168-4a0c-adbe-396f478ad494) and memberOf/any(n:n/id eq 2713f1d5-6822-42bd-ad56-9f6c55a3a8fa)",
|
||||
http.StatusOK),
|
||||
|
||||
Reference in New Issue
Block a user