From 449edd31c8f7f74b22ba8e4dc8fe6dfd590969d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 3 May 2021 15:30:57 +0000 Subject: [PATCH] initial /me/drives implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- graph/pkg/config/config.go | 25 +++-- graph/pkg/flagset/flagset.go | 11 +++ graph/pkg/service/v0/drives.go | 164 +++++++++++++++++++++++++++++--- graph/pkg/service/v0/service.go | 1 + 4 files changed, 176 insertions(+), 25 deletions(-) diff --git a/graph/pkg/config/config.go b/graph/pkg/config/config.go index b2f5f58bb..0e628bcd7 100644 --- a/graph/pkg/config/config.go +++ b/graph/pkg/config/config.go @@ -63,18 +63,23 @@ type Reva struct { Address string } +type Spaces struct { + WebDavBase string +} + // Config combines all available configuration parts. type Config struct { - File string - WebdavNamespace string - Log Log - Debug Debug - HTTP HTTP - Server Server - Tracing Tracing - Ldap Ldap - OpenIDConnect OpenIDConnect - Reva Reva + File string + WebdavNamespace string + Log Log + Debug Debug + HTTP HTTP + Server Server + Tracing Tracing + Ldap Ldap + OpenIDConnect OpenIDConnect + Reva Reva + Spaces Spaces Context context.Context Supervised bool diff --git a/graph/pkg/flagset/flagset.go b/graph/pkg/flagset/flagset.go index 495eb7b6b..f53b0788d 100644 --- a/graph/pkg/flagset/flagset.go +++ b/graph/pkg/flagset/flagset.go @@ -140,6 +140,15 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"GRAPH_HTTP_NAMESPACE"}, Destination: &cfg.HTTP.Namespace, }, + + &cli.StringFlag{ + Name: "spaces-webdav-base", + Value: flags.OverrideDefaultString(cfg.Spaces.WebDavBase, "https://localhost:9200/dav/spaces/"), + Usage: "spaces webdav base URL", + EnvVars: []string{"GRAPH_SPACES_WEBDAV_BASE"}, + Destination: &cfg.Spaces.WebDavBase, + }, + &cli.StringFlag{ Name: "ldap-network", Value: flags.OverrideDefaultString(cfg.Ldap.Network, "tcp"), @@ -182,6 +191,7 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"GRAPH_LDAP_BASEDN_GROUPS"}, Destination: &cfg.Ldap.BaseDNGroups, }, + &cli.StringFlag{ Name: "oidc-endpoint", Value: flags.OverrideDefaultString(cfg.OpenIDConnect.Endpoint, "https://localhost:9200"), @@ -202,6 +212,7 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"GRAPH_OIDC_REALM"}, Destination: &cfg.OpenIDConnect.Realm, }, + &cli.StringFlag{ Name: "reva-gateway-addr", Value: flags.OverrideDefaultString(cfg.Reva.Address, "127.0.0.1:9142"), diff --git a/graph/pkg/service/v0/drives.go b/graph/pkg/service/v0/drives.go index d818c7c44..d1b826af7 100644 --- a/graph/pkg/service/v0/drives.go +++ b/graph/pkg/service/v0/drives.go @@ -1,16 +1,18 @@ package svc import ( + "math" "net/http" + "net/url" "strings" "time" "github.com/go-chi/render" "google.golang.org/grpc/metadata" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/token" msgraph "github.com/yaegashi/msgraph.go/v1.0" ) @@ -33,6 +35,71 @@ func getToken(r *http.Request) string { return tokens[0] } +// GetDrives implements the Service interface. +func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) { + g.logger.Info().Msgf("Calling GetDrives") + accessToken := getToken(r) + if accessToken == "" { + g.logger.Error().Msg("no access token provided in request") + w.WriteHeader(http.StatusForbidden) + return + } + ctx := r.Context() + + client, err := g.GetClient() + if err != nil { + g.logger.Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + t := r.Header.Get("x-access-token") + ctx = token.ContextSetToken(ctx, t) + ctx = metadata.AppendToOutgoingContext(ctx, "x-access-token", t) + + req := &storageprovider.ListStorageSpacesRequest{ + + Filters: []*storageprovider.ListStorageSpacesRequest_Filter{ + { + Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_ID, + Term: &storageprovider.ListStorageSpacesRequest_Filter_Id{ + Id: &storageprovider.StorageSpaceId{ + OpaqueId: "1284d238-aa92-42ce-bdc4-0b0000009157", // FIXME dynamically discover home and other storages ... actually the storage registry should provide the list of storage spaces + }, + }, + }, + }, + } + + res, err := client.ListStorageSpaces(ctx, req) + if err != nil { + g.logger.Error().Err(err).Msg("error sending list storage spaces grpc request") + w.WriteHeader(http.StatusInternalServerError) + return + } + if res.Status.Code != cs3rpc.Code_CODE_OK { + g.logger.Error().Err(err).Msg("error calling grpc list storage spaces") + w.WriteHeader(http.StatusInternalServerError) + return + } + + wdu, err := url.Parse(g.config.Spaces.WebDavBase) + if err != nil { + g.logger.Error().Err(err).Msgf("error parsing url", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + files, err := formatDrives(wdu, res.StorageSpaces) + if err != nil { + g.logger.Error().Err(err).Msgf("error encoding response as json %s", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, &listResponse{Value: files}) +} + // GetRootDriveChildren implements the Service interface. func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { g.logger.Info().Msgf("Calling GetRootDriveChildren") @@ -53,15 +120,9 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { return } - // get reva token - authReq := &gateway.AuthenticateRequest{ - Type: "bearer", - ClientSecret: accessToken, - } - - authRes, _ := client.Authenticate(ctx, authReq); - ctx = token.ContextSetToken(ctx, authRes.Token) - ctx = metadata.AppendToOutgoingContext(ctx, "x-access-token", authRes.Token) + t := r.Header.Get("x-access-token") + ctx = token.ContextSetToken(ctx, t) + ctx = metadata.AppendToOutgoingContext(ctx, "x-access-token", t) g.logger.Info().Msgf("provides access token %v", ctx) @@ -95,13 +156,14 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &listResponse{Value: files}) } +func cs3TimestampToTime(t *types.Timestamp) time.Time { + return time.Unix(int64(t.Seconds), int64(t.Nanos)) +} func cs3ResourceToDriveItem(res *storageprovider.ResourceInfo) (*msgraph.DriveItem, error) { size := new(int) *size = int(res.Size) // uint64 -> int :boom: name := strings.TrimPrefix(res.Path, "/home/") - lastModified := new(time.Time) - *lastModified = time.Unix(int64(res.Mtime.Seconds), int64(res.Mtime.Nanos)) driveItem := &msgraph.DriveItem{ BaseItem: msgraph.BaseItem{ @@ -110,19 +172,21 @@ func cs3ResourceToDriveItem(res *storageprovider.ResourceInfo) (*msgraph.DriveIt ID: &res.Id.OpaqueId, }, Name: &name, - LastModifiedDateTime: lastModified, ETag: &res.Etag, }, Size: size, } + if res.Mtime != nil { + lastModified := cs3TimestampToTime(res.Mtime) + driveItem.BaseItem.LastModifiedDateTime = &lastModified + } if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_FILE { driveItem.File = &msgraph.File{ MimeType: &res.MimeType, } } if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER { - driveItem.Folder = &msgraph.Folder{ - } + driveItem.Folder = &msgraph.Folder{} } return driveItem, nil } @@ -139,3 +203,73 @@ func formatDriveItems(mds []*storageprovider.ResourceInfo) ([]*msgraph.DriveItem return responses, nil } + +func cs3StorageSpaceToDrive(baseUrl *url.URL, space *storageprovider.StorageSpace) (*msgraph.Drive, error) { + rootId := space.Root.StorageId + "!" + space.Root.OpaqueId + drive := &msgraph.Drive{ + BaseItem: msgraph.BaseItem{ + Entity: msgraph.Entity{ + ID: &space.Id.OpaqueId, + }, + Name: &space.Name, + //"createdDateTime": "string (timestamp)", // TODO read from StorageSpace ... needs Opaque for now + //"description": "string", // TODO read from StorageSpace ... needs Opaque for now + }, + Owner: &msgraph.IdentitySet{ + User: &msgraph.Identity{ + ID: &space.Owner.Id.OpaqueId, + // DisplayName: , TODO read and cache from users provider + }, + }, + + DriveType: &space.SpaceType, + Root: &msgraph.DriveItem{ + BaseItem: msgraph.BaseItem{ + Entity: msgraph.Entity{ + ID: &rootId, + }, + }, + }, + } + + if baseUrl != nil { + // TODO read from StorageSpace ... needs Opaque for now + // TODO how do we build the url? + // for now: read from request + webDavURL := baseUrl.String() + rootId + drive.Root.WebDavURL = &webDavURL + } + + if space.Mtime != nil { + lastModified := cs3TimestampToTime(space.Mtime) + drive.BaseItem.LastModifiedDateTime = &lastModified + } + if space.Quota != nil { + // FIXME use https://github.com/owncloud/open-graph-api and return proper int64 + var t int + if space.Quota.QuotaMaxBytes > math.MaxInt32 { + t = math.MaxInt32 + } else { + t = int(space.Quota.QuotaMaxBytes) + } + drive.Quota = &msgraph.Quota{ + Total: &t, + } + } + // FIXME use coowner from https://github.com/owncloud/open-graph-api + + return drive, nil +} + +func formatDrives(baseUrl *url.URL, mds []*storageprovider.StorageSpace) ([]*msgraph.Drive, error) { + responses := make([]*msgraph.Drive, 0, len(mds)) + for i := range mds { + res, err := cs3StorageSpaceToDrive(baseUrl, mds[i]) + if err != nil { + return nil, err + } + responses = append(responses, res) + } + + return responses, nil +} diff --git a/graph/pkg/service/v0/service.go b/graph/pkg/service/v0/service.go index abb397e47..8ea667b16 100644 --- a/graph/pkg/service/v0/service.go +++ b/graph/pkg/service/v0/service.go @@ -33,6 +33,7 @@ func NewService(opts ...Option) Service { r.Route("/v1.0", func(r chi.Router) { r.Route("/me", func(r chi.Router) { r.Get("/", svc.GetMe) + r.Get("/drives", svc.GetDrives) r.Get("/drive/root/children", svc.GetRootDriveChildren) }) r.Route("/users", func(r chi.Router) {