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) }) })