diff --git a/extensions/graph/pkg/identity/backend.go b/extensions/graph/pkg/identity/backend.go index 49e71709b..db003922e 100644 --- a/extensions/graph/pkg/identity/backend.go +++ b/extensions/graph/pkg/identity/backend.go @@ -15,14 +15,14 @@ type Backend interface { DeleteUser(ctx context.Context, nameOrID string) error // UpdateUser applies changes to given user, identified by username or id UpdateUser(ctx context.Context, nameOrID string, user libregraph.User) (*libregraph.User, error) - GetUser(ctx context.Context, nameOrID string) (*libregraph.User, error) + GetUser(ctx context.Context, nameOrID string, queryParam url.Values) (*libregraph.User, error) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) // CreateGroup creates the supplied group in the identity backend. CreateGroup(ctx context.Context, group libregraph.Group) (*libregraph.Group, error) // DeleteGroup deletes a given group, identified by id DeleteGroup(ctx context.Context, id string) error - GetGroup(ctx context.Context, nameOrID string) (*libregraph.Group, error) + GetGroup(ctx context.Context, nameOrID string, queryParam url.Values) (*libregraph.Group, error) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregraph.Group, error) GetGroupMembers(ctx context.Context, id string) ([]*libregraph.User, error) // AddMembersToGroup adds new members (reference by a slice of IDs) to supplied group in the identity backend. diff --git a/extensions/graph/pkg/identity/cs3.go b/extensions/graph/pkg/identity/cs3.go index 7a6cb282c..8c2dd73c6 100644 --- a/extensions/graph/pkg/identity/cs3.go +++ b/extensions/graph/pkg/identity/cs3.go @@ -39,7 +39,7 @@ func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.U return nil, errNotImplemented } -func (i *CS3) GetUser(ctx context.Context, userID string) (*libregraph.User, error) { +func (i *CS3) GetUser(ctx context.Context, userID string, queryParam url.Values) (*libregraph.User, error) { client, err := pool.GetGatewayServiceClient(i.Config.Address) if err != nil { i.Logger.Error().Err(err).Msg("could not get client") @@ -147,7 +147,7 @@ func (i *CS3) CreateGroup(ctx context.Context, group libregraph.Group) (*libregr return nil, errorcode.New(errorcode.NotSupported, "not implemented") } -func (i *CS3) GetGroup(ctx context.Context, groupID string) (*libregraph.Group, error) { +func (i *CS3) GetGroup(ctx context.Context, groupID string, queryParam url.Values) (*libregraph.Group, error) { client, err := pool.GetGatewayServiceClient(i.Config.Address) if err != nil { i.Logger.Error().Err(err).Msg("could not get client") diff --git a/extensions/graph/pkg/identity/ldap.go b/extensions/graph/pkg/identity/ldap.go index bd195a634..4f540dae4 100644 --- a/extensions/graph/pkg/identity/ldap.go +++ b/extensions/graph/pkg/identity/ldap.go @@ -355,13 +355,32 @@ func (i *LDAP) getLDAPUserByFilter(filter string) (*ldap.Entry, error) { return res.Entries[0], nil } -func (i *LDAP) GetUser(ctx context.Context, nameOrID string) (*libregraph.User, error) { +func (i *LDAP) GetUser(ctx context.Context, nameOrID string, queryParam url.Values) (*libregraph.User, error) { i.logger.Debug().Str("backend", "ldap").Msg("GetUser") e, err := i.getLDAPUserByNameOrID(nameOrID) if err != nil { return nil, err } - return i.createUserModelFromLDAP(e), nil + userGroups, err := i.getGroupsForUser(e.DN) + if err != nil { + return nil, err + } + sel := strings.Split(queryParam.Get("$select"), ",") + exp := strings.Split(queryParam.Get("$expand"), ",") + u := i.createUserModelFromLDAP(e) + if slices.Contains(sel, "memberOf") || slices.Contains(exp, "memberOf") { + if err != nil { + return nil, err + } + if len(userGroups) > 0 { + groups := make([]libregraph.Group, 0, len(userGroups)) + for _, g := range userGroups { + groups = append(groups, *i.createGroupModelFromLDAP(g)) + } + u.MemberOf = groups + } + } + return u, nil } func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) { @@ -406,11 +425,7 @@ func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregra exp := strings.Split(queryParam.Get("$expand"), ",") u := i.createUserModelFromLDAP(e) if slices.Contains(sel, "memberOf") || slices.Contains(exp, "memberOf") { - groupFilter := fmt.Sprintf( - "(%s=%s)", - i.groupAttributeMap.member, e.DN, - ) - userGroups, err := i.getLDAPGroupsByFilter(groupFilter, false, false) + userGroups, err := i.getGroupsForUser(e.DN) if err != nil { return nil, err } @@ -431,13 +446,41 @@ func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregra return users, nil } -func (i *LDAP) GetGroup(ctx context.Context, nameOrID string) (*libregraph.Group, error) { +func (i *LDAP) getGroupsForUser(dn string) ([]*ldap.Entry, error) { + groupFilter := fmt.Sprintf( + "(%s=%s)", + i.groupAttributeMap.member, dn, + ) + userGroups, err := i.getLDAPGroupsByFilter(groupFilter, false, false) + if err != nil { + return nil, err + } + return userGroups, nil +} + +func (i *LDAP) GetGroup(ctx context.Context, nameOrID string, queryParam url.Values) (*libregraph.Group, error) { i.logger.Debug().Str("backend", "ldap").Msg("GetGroup") e, err := i.getLDAPGroupByNameOrID(nameOrID, true) if err != nil { return nil, err } - return i.createGroupModelFromLDAP(e), nil + sel := strings.Split(queryParam.Get("$select"), ",") + exp := strings.Split(queryParam.Get("$expand"), ",") + g := i.createGroupModelFromLDAP(e) + if slices.Contains(sel, "members") || slices.Contains(exp, "members") { + members, err := i.GetGroupMembers(ctx, *g.Id) + if err != nil { + return nil, err + } + if len(members) > 1 { + m := make([]libregraph.User, 0, len(members)) + for _, u := range members { + m = append(m, *u) + } + g.Members = m + } + } + return g, nil } func (i *LDAP) getLDAPGroupByID(id string, requestMembers bool) (*ldap.Entry, error) { @@ -581,7 +624,6 @@ func (i *LDAP) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregr exp := strings.Split(queryParam.Get("$expand"), ",") g := i.createGroupModelFromLDAP(e) if slices.Contains(sel, "members") || slices.Contains(exp, "members") { - members, err := i.GetGroupMembers(ctx, *g.Id) if err != nil { return nil, err diff --git a/extensions/graph/pkg/identity/ldap_test.go b/extensions/graph/pkg/identity/ldap_test.go index d272eb6e3..aac54eca4 100644 --- a/extensions/graph/pkg/identity/ldap_test.go +++ b/extensions/graph/pkg/identity/ldap_test.go @@ -125,7 +125,24 @@ func TestGetUser(t *testing.T) { return nil, ldap.NewError(ldap.LDAPResultSizeLimitExceeded, errors.New("mock")) } b, _ := getMockedBackend(&sf, lconfig, &logger) - _, err := b.GetUser(context.Background(), "fred") + + queryParamExpand := url.Values{ + "$expand": []string{"memberOf"}, + } + queryParamSelect := url.Values{ + "$select": []string{"memberOf"}, + } + _, err := b.GetUser(context.Background(), "fred", nil) + if err == nil || err.Error() != "itemNotFound" { + t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) + } + + _, err = b.GetUser(context.Background(), "fred", queryParamExpand) + if err == nil || err.Error() != "itemNotFound" { + t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) + } + + _, err = b.GetUser(context.Background(), "fred", queryParamSelect) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } @@ -135,7 +152,17 @@ func TestGetUser(t *testing.T) { return &ldap.SearchResult{}, nil } b, _ = getMockedBackend(&sf, lconfig, &logger) - _, err = b.GetUser(context.Background(), "fred") + _, err = b.GetUser(context.Background(), "fred", nil) + if err == nil || err.Error() != "itemNotFound" { + t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) + } + + _, err = b.GetUser(context.Background(), "fred", queryParamExpand) + if err == nil || err.Error() != "itemNotFound" { + t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) + } + + _, err = b.GetUser(context.Background(), "fred", queryParamSelect) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } @@ -147,7 +174,21 @@ func TestGetUser(t *testing.T) { }, nil } b, _ = getMockedBackend(&sf, lconfig, &logger) - u, err := b.GetUser(context.Background(), "user") + u, err := b.GetUser(context.Background(), "user", nil) + if err != nil { + t.Errorf("Expected GetUser to succeed. Got %s", err.Error()) + } else if *u.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) { + t.Errorf("Expected GetUser to return a valid user") + } + + u, err = b.GetUser(context.Background(), "user", queryParamExpand) + if err != nil { + t.Errorf("Expected GetUser to succeed. Got %s", err.Error()) + } else if *u.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) { + t.Errorf("Expected GetUser to return a valid user") + } + + u, err = b.GetUser(context.Background(), "user", queryParamSelect) if err != nil { t.Errorf("Expected GetUser to succeed. Got %s", err.Error()) } else if *u.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) { @@ -182,8 +223,22 @@ func TestGetGroup(t *testing.T) { var sf searchFunc = func(*ldap.SearchRequest) (*ldap.SearchResult, error) { return nil, ldap.NewError(ldap.LDAPResultSizeLimitExceeded, errors.New("mock")) } + queryParamExpand := url.Values{ + "$expand": []string{"memberOf"}, + } + queryParamSelect := url.Values{ + "$select": []string{"memberOf"}, + } b, _ := getMockedBackend(&sf, lconfig, &logger) - _, err := b.GetGroup(context.Background(), "group") + _, err := b.GetGroup(context.Background(), "group", nil) + if err == nil || err.Error() != "itemNotFound" { + t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) + } + _, err = b.GetGroup(context.Background(), "group", queryParamExpand) + if err == nil || err.Error() != "itemNotFound" { + t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) + } + _, err = b.GetGroup(context.Background(), "group", queryParamSelect) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } @@ -193,7 +248,15 @@ func TestGetGroup(t *testing.T) { return &ldap.SearchResult{}, nil } b, _ = getMockedBackend(&sf, lconfig, &logger) - _, err = b.GetGroup(context.Background(), "group") + _, err = b.GetGroup(context.Background(), "group", nil) + if err == nil || err.Error() != "itemNotFound" { + t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) + } + _, err = b.GetGroup(context.Background(), "group", queryParamExpand) + if err == nil || err.Error() != "itemNotFound" { + t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) + } + _, err = b.GetGroup(context.Background(), "group", queryParamSelect) if err == nil || err.Error() != "itemNotFound" { t.Errorf("Expected 'itemNotFound' got '%s'", err.Error()) } @@ -205,7 +268,19 @@ func TestGetGroup(t *testing.T) { }, nil } b, _ = getMockedBackend(&sf, lconfig, &logger) - g, err := b.GetGroup(context.Background(), "group") + g, err := b.GetGroup(context.Background(), "group", nil) + if err != nil { + t.Errorf("Expected GetGroup to succeed. Got %s", err.Error()) + } else if *g.Id != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id) { + t.Errorf("Expected GetGroup to return a valid group") + } + g, err = b.GetGroup(context.Background(), "group", queryParamExpand) + if err != nil { + t.Errorf("Expected GetGroup to succeed. Got %s", err.Error()) + } else if *g.Id != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id) { + t.Errorf("Expected GetGroup to return a valid group") + } + g, err = b.GetGroup(context.Background(), "group", queryParamSelect) if err != nil { t.Errorf("Expected GetGroup to succeed. Got %s", err.Error()) } else if *g.Id != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id) { diff --git a/extensions/graph/pkg/service/v0/groups.go b/extensions/graph/pkg/service/v0/groups.go index 14a6e9461..3fef37505 100644 --- a/extensions/graph/pkg/service/v0/groups.go +++ b/extensions/graph/pkg/service/v0/groups.go @@ -162,7 +162,7 @@ func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) { return } - group, err := g.identityBackend.GetGroup(r.Context(), groupID) + group, err := g.identityBackend.GetGroup(r.Context(), groupID, r.URL.Query()) if err != nil { var errcode errorcode.Error if errors.As(err, &errcode) { diff --git a/extensions/graph/pkg/service/v0/users.go b/extensions/graph/pkg/service/v0/users.go index e272bdf42..7e2802019 100644 --- a/extensions/graph/pkg/service/v0/users.go +++ b/extensions/graph/pkg/service/v0/users.go @@ -159,7 +159,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { return } - user, err := g.identityBackend.GetUser(r.Context(), userID) + user, err := g.identityBackend.GetUser(r.Context(), userID, r.URL.Query()) if err != nil { var errcode errorcode.Error