From f253f7bc8eb3a1d06ff28eeea34e9acd27178e62 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Mon, 22 Nov 2021 16:36:29 +0100 Subject: [PATCH] graph: Groups convert CS3 code to its own backend Until the LDAP backend is ready the existing CS3 code should keep working. This also adds the initial stubs for the upcoming LDAP Backend. --- graph/pkg/identity/backend.go | 3 + graph/pkg/identity/cs3.go | 91 ++++++++++++++++++++++- graph/pkg/identity/ldap.go | 8 ++ graph/pkg/service/v0/graph.go | 9 --- graph/pkg/service/v0/groups.go | 127 ++++++-------------------------- graph/pkg/service/v0/service.go | 1 - 6 files changed, 123 insertions(+), 116 deletions(-) diff --git a/graph/pkg/identity/backend.go b/graph/pkg/identity/backend.go index fe3897ce7..b5e6620bf 100644 --- a/graph/pkg/identity/backend.go +++ b/graph/pkg/identity/backend.go @@ -11,6 +11,9 @@ import ( type Backend interface { GetUser(ctx context.Context, nameOrId string) (*msgraph.User, error) GetUsers(ctx context.Context, queryParam url.Values) ([]*msgraph.User, error) + + GetGroup(ctx context.Context, nameOrId string) (*msgraph.Group, error) + GetGroups(ctx context.Context, queryParam url.Values) ([]*msgraph.Group, error) } func CreateUserModelFromCS3(u *cs3.User) *msgraph.User { diff --git a/graph/pkg/identity/cs3.go b/graph/pkg/identity/cs3.go index effaf5b9c..b81f39fa5 100644 --- a/graph/pkg/identity/cs3.go +++ b/graph/pkg/identity/cs3.go @@ -4,7 +4,8 @@ import ( "context" "net/url" - cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" msgraph "github.com/yaegashi/msgraph.go/beta" @@ -26,7 +27,7 @@ func (i *CS3) GetUser(ctx context.Context, userID string) (*msgraph.User, error) return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) } - res, err := client.GetUserByClaim(ctx, &cs3.GetUserByClaimRequest{ + res, err := client.GetUserByClaim(ctx, &cs3user.GetUserByClaimRequest{ Claim: "userid", // FIXME add consts to reva Value: userID, }) @@ -57,7 +58,7 @@ func (i *CS3) GetUsers(ctx context.Context, queryParam url.Values) ([]*msgraph.U search = queryParam.Get("$search") } - res, err := client.FindUsers(ctx, &cs3.FindUsersRequest{ + res, err := client.FindUsers(ctx, &cs3user.FindUsersRequest{ // FIXME presence match is currently not implemented, an empty search currently leads to // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented Filter: search, @@ -82,3 +83,87 @@ func (i *CS3) GetUsers(ctx context.Context, queryParam url.Values) ([]*msgraph.U return users, nil } + +func (i *CS3) GetGroups(ctx context.Context, queryParam url.Values) ([]*msgraph.Group, error) { + client, err := pool.GetGatewayServiceClient(i.Config.Address) + if err != nil { + i.Logger.Error().Err(err).Msg("could not get client") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + } + + search := queryParam.Get("search") + if search == "" { + search = queryParam.Get("$search") + } + + res, err := client.FindGroups(ctx, &cs3group.FindGroupsRequest{ + // FIXME presence match is currently not implemented, an empty search currently leads to + // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented + Filter: search, + }) + + switch { + case err != nil: + i.Logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) + } + i.Logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") + return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) + } + + groups := make([]*msgraph.Group, 0, len(res.Groups)) + + for _, group := range res.Groups { + groups = append(groups, createGroupModelFromCS3(group)) + } + + return groups, nil +} + +func (i *CS3) GetGroup(ctx context.Context, groupID string) (*msgraph.Group, error) { + client, err := pool.GetGatewayServiceClient(i.Config.Address) + if err != nil { + i.Logger.Error().Err(err).Msg("could not get client") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + } + + res, err := client.GetGroupByClaim(ctx, &cs3group.GetGroupByClaimRequest{ + Claim: "groupid", // FIXME add consts to reva + Value: groupID, + }) + + switch { + case err != nil: + i.Logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) + } + i.Logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") + return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) + } + + return createGroupModelFromCS3(res.Group), nil +} + +func createGroupModelFromCS3(g *cs3group.Group) *msgraph.Group { + if g.Id == nil { + g.Id = &cs3group.GroupId{} + } + return &msgraph.Group{ + DirectoryObject: msgraph.DirectoryObject{ + Entity: msgraph.Entity{ + ID: &g.Id.OpaqueId, + }, + }, + OnPremisesDomainName: &g.Id.Idp, + OnPremisesSamAccountName: &g.GroupName, + DisplayName: &g.DisplayName, + Mail: &g.Mail, + // TODO when to fetch and expand memberof, usernames or ids? + } +} diff --git a/graph/pkg/identity/ldap.go b/graph/pkg/identity/ldap.go index e58da4285..7b92a105a 100644 --- a/graph/pkg/identity/ldap.go +++ b/graph/pkg/identity/ldap.go @@ -136,6 +136,14 @@ func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*msgraph. return users, nil } +func (i *LDAP) GetGroup(ctx context.Context, groupID string) (*msgraph.Group, error) { + return nil, nil +} + +func (i *LDAP) GetGroups(ctx context.Context, queryParam url.Values) ([]*msgraph.Group, error) { + return nil, nil +} + func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *msgraph.User { return &msgraph.User{ DisplayName: pointerOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.displayName)), diff --git a/graph/pkg/service/v0/graph.go b/graph/pkg/service/v0/graph.go index 428d41623..251bd0eda 100644 --- a/graph/pkg/service/v0/graph.go +++ b/graph/pkg/service/v0/graph.go @@ -29,15 +29,6 @@ func (g Graph) GetClient() (gateway.GatewayAPIClient, error) { return pool.GetGatewayServiceClient(g.config.Reva.Address) } -// The key type is unexported to prevent collisions with context keys defined in -// other packages. -type key int - -const ( - userKey key = iota - groupKey -) - type listResponse struct { Value interface{} `json:"value,omitempty"` } diff --git a/graph/pkg/service/v0/groups.go b/graph/pkg/service/v0/groups.go index 9677bdc08..01af9178c 100644 --- a/graph/pkg/service/v0/groups.go +++ b/graph/pkg/service/v0/groups.go @@ -1,129 +1,50 @@ package svc import ( - "context" + "errors" "net/http" - cs3 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" - cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/owncloud/ocis/graph/pkg/service/v0/errorcode" "github.com/go-chi/chi/v5" "github.com/go-chi/render" - //msgraph "github.com/owncloud/open-graph-api-go" // FIXME add groups to open graph, needs OnPremisesSamAccountName and OnPremisesDomainName - msgraph "github.com/yaegashi/msgraph.go/v1.0" ) -// GroupCtx middleware is used to load an User object from -// the URL parameters passed through as the request. In case -// the User could not be found, we stop here and return a 404. -func (g Graph) GroupCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - groupID := chi.URLParam(r, "groupID") - if groupID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") - return - } - - client, err := g.GetClient() - if err != nil { - g.logger.Error().Err(err).Msg("could not get client") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } - - res, err := client.GetGroupByClaim(r.Context(), &cs3.GetGroupByClaimRequest{ - Claim: "groupid", // FIXME add consts to reva - Value: groupID, - }) - - switch { - case err != nil: - g.logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) - return - case res.Status.Code != cs3rpc.Code_CODE_OK: - if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { - errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) - return - } - g.logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) - return - } - - ctx := context.WithValue(r.Context(), groupKey, res.Group) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - // GetGroups implements the Service interface. func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) { - client, err := g.GetClient() + groups, err := g.identityBackend.GetGroups(r.Context(), r.URL.Query()) + if err != nil { - g.logger.Error().Err(err).Msg("could not get client") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } - - search := r.URL.Query().Get("search") - if search == "" { - search = r.URL.Query().Get("$search") - } - - res, err := client.FindGroups(r.Context(), &cs3.FindGroupsRequest{ - // FIXME presence match is currently not implemented, an empty search currently leads to - // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented - Filter: search, - }) - switch { - case err != nil: - g.logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) - return - case res.Status.Code != cs3rpc.Code_CODE_OK: - if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { - errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) - return + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) } - g.logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) - return } - - groups := make([]*msgraph.Group, 0, len(res.Groups)) - - for _, group := range res.Groups { - groups = append(groups, createGroupModelFromCS3(group)) - } - render.Status(r, http.StatusOK) render.JSON(w, r, &listResponse{Value: groups}) } // GetGroup implements the Service interface. func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) { - group := r.Context().Value(groupKey).(*cs3.Group) + groupID := chi.URLParam(r, "groupID") + if groupID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") + return + } + + group, err := g.identityBackend.GetGroup(r.Context(), groupID) + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + } render.Status(r, http.StatusOK) - render.JSON(w, r, createGroupModelFromCS3(group)) -} - -func createGroupModelFromCS3(g *cs3.Group) *msgraph.Group { - if g.Id == nil { - g.Id = &cs3.GroupId{} - } - return &msgraph.Group{ - DirectoryObject: msgraph.DirectoryObject{ - Entity: msgraph.Entity{ - ID: &g.Id.OpaqueId, - }, - }, - OnPremisesDomainName: &g.Id.Idp, - OnPremisesSamAccountName: &g.GroupName, - DisplayName: &g.DisplayName, - Mail: &g.Mail, - // TODO when to fetch and expand memberof, usernames or ids? - } + render.JSON(w, r, group) } diff --git a/graph/pkg/service/v0/service.go b/graph/pkg/service/v0/service.go index 9762845be..9e9927d30 100644 --- a/graph/pkg/service/v0/service.go +++ b/graph/pkg/service/v0/service.go @@ -62,7 +62,6 @@ func NewService(opts ...Option) Service { r.Route("/groups", func(r chi.Router) { r.Get("/", svc.GetGroups) r.Route("/{groupID}", func(r chi.Router) { - r.Use(svc.GroupCtx) r.Get("/", svc.GetGroup) }) })