diff --git a/changelog/unreleased/appprovider-stat.md b/changelog/unreleased/appprovider-stat.md new file mode 100644 index 0000000000..d71b1eb7dd --- /dev/null +++ b/changelog/unreleased/appprovider-stat.md @@ -0,0 +1,7 @@ +Bugfix: Call the gateway stat method from appprovider + +The appprovider service used to directly pass the stat request to the storage +provider bypassing the gateway, which resulted in errors while handling share +children as they are resolved in the gateway path. + +https://github.com/cs3org/reva/pull/1140 diff --git a/cmd/reva/configure.go b/cmd/reva/configure.go index 04b009299a..0795a84daa 100644 --- a/cmd/reva/configure.go +++ b/cmd/reva/configure.go @@ -36,8 +36,8 @@ var configureCommand = func() *command { return err } - c := &config{Host: text} - if err := writeConfig(c); err != nil { + conf = &config{Host: text} + if err := writeConfig(conf); err != nil { return err } fmt.Println("config saved at ", getConfigFile()) diff --git a/cmd/reva/main.go b/cmd/reva/main.go index ae85318b09..992c5cfee1 100644 --- a/cmd/reva/main.go +++ b/cmd/reva/main.go @@ -75,8 +75,8 @@ var ( func init() { flag.StringVar(&host, "host", "", "address of the GRPC gateway host") flag.BoolVar(&insecure, "insecure", false, "disables grpc transport security") - flag.BoolVar(&skipverify, "skip-verify", false, "whether a client verifies the server's certificate chain and host name.") - flag.BoolVar(&disableargprompt, "disable-arg-prompt", false, "whether to disable prompts for command arguments.") + flag.BoolVar(&skipverify, "skip-verify", false, "whether to skip verifying the server's certificate chain and host name") + flag.BoolVar(&disableargprompt, "disable-arg-prompt", false, "whether to disable prompts for command arguments") flag.IntVar(&timeout, "timout", -1, "the timeout in seconds for executing the commands, -1 means no timeout") flag.Parse() } diff --git a/cmd/reva/open-file-in-app-provider.go b/cmd/reva/open-file-in-app-provider.go index 0a04dd9a76..9c23f53e7e 100644 --- a/cmd/reva/open-file-in-app-provider.go +++ b/cmd/reva/open-file-in-app-provider.go @@ -25,6 +25,7 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/pkg/errors" ) @@ -35,9 +36,13 @@ func openFileInAppProviderCommand() *command { return "Usage: open-file-in-app-provider [-flags] [-viewmode view|read|write] " } viewMode := cmd.String("viewmode", "view", "the view permissions, defaults to view") + insecureFlag := cmd.Bool("insecure", false, "disables grpc transport security") + skipVerifyFlag := cmd.Bool("skip-verify", false, "whether to skip verifying remote reva's certificate chain and host name") cmd.ResetFlags = func() { *viewMode = "view" + *insecureFlag = false + *skipVerifyFlag = false } cmd.Action = func(w ...io.Writer) error { @@ -58,7 +63,17 @@ func openFileInAppProviderCommand() *command { Spec: &provider.Reference_Path{Path: path}, } - openRequest := &gateway.OpenFileInAppProviderRequest{Ref: ref, ViewMode: vm} + opaqueObj := &typespb.Opaque{ + Map: map[string]*typespb.OpaqueEntry{}, + } + if *insecureFlag { + opaqueObj.Map["insecure"] = &typespb.OpaqueEntry{} + } + if *skipVerifyFlag { + opaqueObj.Map["skip-verify"] = &typespb.OpaqueEntry{} + } + + openRequest := &gateway.OpenFileInAppProviderRequest{Ref: ref, ViewMode: vm, Opaque: opaqueObj} openRes, err := client.OpenFileInAppProvider(ctx, openRequest) if err != nil { diff --git a/docs/content/en/docs/config/grpc/services/appprovider/_index.md b/docs/content/en/docs/config/grpc/services/appprovider/_index.md index 2e892a3851..69b18917b6 100644 --- a/docs/content/en/docs/config/grpc/services/appprovider/_index.md +++ b/docs/content/en/docs/config/grpc/services/appprovider/_index.md @@ -9,7 +9,7 @@ description: > # _struct: config_ {{% dir name="iopsecret" type="string" default="" %}} -The iopsecret used to connect to the wopiserver. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/appprovider/appprovider.go#L60) +The iopsecret used to connect to the wopiserver. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/appprovider/appprovider.go#L58) {{< highlight toml >}} [grpc.services.appprovider] iopsecret = "" @@ -17,7 +17,7 @@ iopsecret = "" {{% /dir %}} {{% dir name="wopiurl" type="string" default="" %}} -The wopiserver's URL. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/appprovider/appprovider.go#L61) +The wopiserver's URL. [[Ref]](https://github.com/cs3org/reva/tree/master/internal/grpc/services/appprovider/appprovider.go#L59) {{< highlight toml >}} [grpc.services.appprovider] wopiurl = "" diff --git a/examples/ocmd/providers.demo.json b/examples/ocmd/providers.demo.json index 79e82355e1..05aa6c78d3 100644 --- a/examples/ocmd/providers.demo.json +++ b/examples/ocmd/providers.demo.json @@ -32,6 +32,19 @@ }, "api_version": "0.0.1", "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Gateway", + "description": "CERNBox GRPC Gateway" + }, + "name": "CERNBox - GRPC Gateway", + "path": "127.0.0.1:19000", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "127.0.0.1:19000" } ] }, @@ -68,6 +81,19 @@ }, "api_version": "0.0.1", "host": "http://127.0.0.1:17001/" + }, + { + "endpoint": { + "type": { + "name": "Gateway", + "description": "CESNET GRPC Gateway" + }, + "name": "CESNET - GRPC Gateway", + "path": "127.0.0.1:17000", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "127.0.0.1:17000" } ] }, @@ -104,6 +130,19 @@ }, "api_version": "0.0.1", "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Gateway", + "description": "Example GRPC Gateway" + }, + "name": "Example - GRPC Gateway", + "path": "127.0.0.1:19000", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "127.0.0.1:19000" } ] }, @@ -140,6 +179,19 @@ }, "api_version": "0.0.1", "host": "http://127.0.0.1:19001/" + }, + { + "endpoint": { + "type": { + "name": "Gateway", + "description": "Test GRPC Gateway" + }, + "name": "Test - GRPC Gateway", + "path": "127.0.0.1:19000", + "is_monitored": true + }, + "api_version": "0.0.1", + "host": "127.0.0.1:19000" } ] } diff --git a/go.sum b/go.sum index 03faf26194..2e64be9143 100644 --- a/go.sum +++ b/go.sum @@ -892,6 +892,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/internal/grpc/services/appprovider/appprovider.go b/internal/grpc/services/appprovider/appprovider.go index 993180caf7..a3bc5463bb 100644 --- a/internal/grpc/services/appprovider/appprovider.go +++ b/internal/grpc/services/appprovider/appprovider.go @@ -22,14 +22,12 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" "net/url" "os" "path" - "strconv" "strings" "time" @@ -135,7 +133,7 @@ func (s *service) getWopiAppEndpoints(ctx context.Context) (map[string]interface } defer appsRes.Body.Close() if appsRes.StatusCode != http.StatusOK { - return nil, errors.New("Request to WOPI server returned " + string(appsRes.StatusCode)) + return nil, fmt.Errorf("Request to WOPI server returned %d", appsRes.StatusCode) } appsBody, err := ioutil.ReadAll(appsRes.Body) if err != nil { @@ -207,7 +205,7 @@ func (s *service) OpenFileInAppProvider(ctx context.Context, req *providerpb.Ope if openRes.StatusCode != http.StatusOK { res := &providerpb.OpenFileInAppProviderResponse{ - Status: status.NewInvalid(ctx, "appprovider: error performing open request to WOPI, status code: "+strconv.Itoa(openRes.StatusCode)), + Status: status.NewInvalid(ctx, fmt.Sprintf("appprovider: error performing open request to WOPI, status code: %d", openRes.StatusCode)), } return res, nil } diff --git a/internal/grpc/services/gateway/appprovider.go b/internal/grpc/services/gateway/appprovider.go index aa4c0933a1..57e98453e2 100644 --- a/internal/grpc/services/gateway/appprovider.go +++ b/internal/grpc/services/gateway/appprovider.go @@ -20,47 +20,59 @@ package gateway import ( "context" - "fmt" + "crypto/tls" + "net/url" + "strings" providerpb "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - tokenpkg "github.com/cs3org/reva/pkg/token" + "github.com/cs3org/reva/pkg/token" "github.com/pkg/errors" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" ) func (s *svc) OpenFileInAppProvider(ctx context.Context, req *gateway.OpenFileInAppProviderRequest) (*providerpb.OpenFileInAppProviderResponse, error) { - c, err := s.find(ctx, req.Ref) - if err != nil { - if _, ok := err.(errtypes.IsNotFound); ok { + p, st := s.getPath(ctx, req.Ref) + if st.Code != rpc.Code_CODE_OK { + if st.Code == rpc.Code_CODE_NOT_FOUND { return &providerpb.OpenFileInAppProviderResponse{ - Status: status.NewInternal(ctx, err, "storage provider not found"), + Status: status.NewNotFound(ctx, "gateway: file not found:"+req.Ref.String()), }, nil } return &providerpb.OpenFileInAppProviderResponse{ - Status: status.NewInternal(ctx, err, "error finding storage provider"), + Status: st, }, nil } - accessToken, ok := tokenpkg.ContextGetToken(ctx) - if !ok || accessToken == "" { + if s.isSharedFolder(ctx, p) { return &providerpb.OpenFileInAppProviderResponse{ - Status: status.NewUnauthenticated(ctx, err, "Access token is invalid or empty"), + Status: status.NewInvalid(ctx, "gateway: can't open shares folder"), }, nil } - statReq := &provider.StatRequest{ - Ref: req.Ref, + resName, resChild := p, "" + if s.isShareChild(ctx, p) { + resName, resChild = s.splitShare(ctx, p) } - statRes, err := c.Stat(ctx, statReq) + statRes, err := s.stat(ctx, &storageprovider.StatRequest{ + Ref: &storageprovider.Reference{ + Spec: &storageprovider.Reference_Path{ + Path: resName, + }, + }, + }) if err != nil { return &providerpb.OpenFileInAppProviderResponse{ Status: status.NewInternal(ctx, err, "gateway: error calling Stat on the resource path for the app provider: "+req.Ref.GetPath()), @@ -75,7 +87,105 @@ func (s *svc) OpenFileInAppProvider(ctx context.Context, req *gateway.OpenFileIn fileInfo := statRes.Info - provider, err := s.findAppProvider(ctx, fileInfo) + // The file is a share + if fileInfo.Type == storageprovider.ResourceType_RESOURCE_TYPE_REFERENCE { + uri, err := url.Parse(fileInfo.Target) + if err != nil { + return &providerpb.OpenFileInAppProviderResponse{ + Status: status.NewInternal(ctx, err, "gateway: error parsing target uri: "+fileInfo.Target), + }, nil + } + if uri.Scheme == "webdav" { + insecure, skipVerify := getGRPCConfig(req.Opaque) + return s.openFederatedShares(ctx, fileInfo.Target, req.ViewMode, insecure, skipVerify, resChild) + } + + res, err := s.Stat(ctx, &storageprovider.StatRequest{ + Ref: req.Ref, + }) + if err != nil { + return &providerpb.OpenFileInAppProviderResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling Stat on the resource path for the app provider: "+req.Ref.GetPath()), + }, nil + } + if res.Status.Code != rpc.Code_CODE_OK { + err := status.NewErrorFromCode(res.Status.GetCode(), "gateway") + return &providerpb.OpenFileInAppProviderResponse{ + Status: status.NewInternal(ctx, err, "Stat failed on the resource path for the app provider: "+req.Ref.GetPath()), + }, nil + } + fileInfo = res.Info + } + return s.openLocalResources(ctx, fileInfo, req.ViewMode) +} + +func (s *svc) openFederatedShares(ctx context.Context, targetURL string, vm gateway.OpenFileInAppProviderRequest_ViewMode, + insecure, skipVerify bool, nameQueries ...string) (*providerpb.OpenFileInAppProviderResponse, error) { + log := appctx.GetLogger(ctx) + targetURL, err := appendNameQuery(targetURL, nameQueries...) + if err != nil { + return nil, err + } + ep, err := s.extractEndpointInfo(ctx, targetURL) + if err != nil { + return nil, err + } + + ref := &storageprovider.Reference{ + Spec: &storageprovider.Reference_Path{ + Path: ep.filePath, + }, + } + appProviderReq := &gateway.OpenFileInAppProviderRequest{ + Ref: ref, + ViewMode: vm, + } + + meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ + Domain: ep.endpoint, + }) + if err != nil { + return nil, errors.Wrap(err, "gateway: error calling GetInfoByDomain") + } + var gatewayEP string + for _, s := range meshProvider.ProviderInfo.Services { + if strings.ToLower(s.Endpoint.Type.Name) == "gateway" { + gatewayEP = s.Endpoint.Path + } + } + log.Debug().Msgf("Forwarding OpenFileInAppProvider request to: %s", gatewayEP) + + conn, err := getConn(gatewayEP, insecure, skipVerify) + if err != nil { + err = errors.Wrap(err, "gateway: error connecting to remote reva") + return &providerpb.OpenFileInAppProviderResponse{ + Status: status.NewInternal(ctx, err, "error error connecting to remote reva"), + }, nil + } + + gatewayClient := gateway.NewGatewayAPIClient(conn) + remoteCtx := token.ContextSetToken(context.Background(), ep.token) + remoteCtx = metadata.AppendToOutgoingContext(remoteCtx, token.TokenHeader, ep.token) + + res, err := gatewayClient.OpenFileInAppProvider(remoteCtx, appProviderReq) + if err != nil { + log.Err(err).Msg("error reaching remote reva") + return nil, errors.Wrap(err, "gateway: error calling OpenFileInAppProvider") + } + return res, nil +} + +func (s *svc) openLocalResources(ctx context.Context, ri *storageprovider.ResourceInfo, + vm gateway.OpenFileInAppProviderRequest_ViewMode) (*providerpb.OpenFileInAppProviderResponse, error) { + + accessToken, ok := token.ContextGetToken(ctx) + if !ok || accessToken == "" { + return &providerpb.OpenFileInAppProviderResponse{ + Status: status.NewUnauthenticated(ctx, errors.New("Access token is invalid or empty"), ""), + }, nil + } + + provider, err := s.findAppProvider(ctx, ri) if err != nil { err = errors.Wrap(err, "gateway: error calling findAppProvider") var st *rpc.Status @@ -97,14 +207,9 @@ func (s *svc) OpenFileInAppProvider(ctx context.Context, req *gateway.OpenFileIn }, nil } - // build the appProvider specific request with the required extra info that has been obtained - - log := appctx.GetLogger(ctx) - log.Debug().Msg(fmt.Sprintf("request: %s", req)) - appProviderReq := &providerpb.OpenFileInAppProviderRequest{ - ResourceInfo: fileInfo, - ViewMode: providerpb.OpenFileInAppProviderRequest_ViewMode(req.ViewMode), + ResourceInfo: ri, + ViewMode: providerpb.OpenFileInAppProviderRequest_ViewMode(vm), AccessToken: accessToken, } @@ -143,3 +248,24 @@ func (s *svc) findAppProvider(ctx context.Context, ri *storageprovider.ResourceI return nil, errors.New("gateway: error finding a storage provider") } + +func getGRPCConfig(opaque *typespb.Opaque) (bool, bool) { + if opaque == nil { + return false, false + } + _, insecure := opaque.Map["insecure"] + _, skipVerify := opaque.Map["skip-verify"] + return insecure, skipVerify +} + +func getConn(host string, insecure, skipverify bool) (*grpc.ClientConn, error) { + if insecure { + return grpc.Dial(host, grpc.WithInsecure()) + } + + // TODO(labkode): if in the future we want client-side certificate validation, + // we need to load the client cert here + tlsconf := &tls.Config{InsecureSkipVerify: skipverify} + creds := credentials.NewTLS(tlsconf) + return grpc.Dial(host, grpc.WithTransportCredentials(creds)) +} diff --git a/internal/grpc/services/gateway/ocmshareprovider.go b/internal/grpc/services/gateway/ocmshareprovider.go index 5cdb7e70d2..3063d40247 100644 --- a/internal/grpc/services/gateway/ocmshareprovider.go +++ b/internal/grpc/services/gateway/ocmshareprovider.go @@ -22,9 +22,7 @@ import ( "context" "fmt" "path" - "strings" - ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -288,20 +286,6 @@ func (s *svc) createWebdavReference(ctx context.Context, share *ocm.Share) (*rpc log := appctx.GetLogger(ctx) - meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ - Domain: share.Creator.Idp, - }) - if err != nil { - err := errors.Wrap(err, "gateway: error calling GetInfoByDomain") - return status.NewInternal(ctx, err, "error updating received share"), nil - } - var webdavEndpoint string - for _, s := range meshProvider.ProviderInfo.Services { - if strings.ToLower(s.Endpoint.Type.Name) == "webdav" { - webdavEndpoint = s.Endpoint.Path - } - } - var token string tokenOpaque, ok := share.Grantee.Opaque.Map["token"] if !ok { @@ -330,8 +314,8 @@ func (s *svc) createWebdavReference(ctx context.Context, share *ocm.Share) (*rpc createRefReq := &provider.CreateReferenceRequest{ Path: refPath, - // webdav is the scheme, token@webdav_endpoint the opaque part and the share name the query of the URL. - TargetUri: fmt.Sprintf("webdav:%s@%s?name=%s", token, webdavEndpoint, share.Name), + // webdav is the scheme, token@host the opaque part and the share name the query of the URL. + TargetUri: fmt.Sprintf("webdav://%s@%s?name=%s", token, share.Creator.Idp, share.Name), } c, err := s.findByPath(ctx, refPath) diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 7a15d989dd..1a557a54df 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -182,14 +182,6 @@ func (s *svc) InitiateFileDownload(ctx context.Context, req *provider.InitiateFi }, nil } - if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - err := errors.New(fmt.Sprintf("gateway: expected reference: got:%+v", statRes.Info)) - log.Err(err).Msg("gateway: error creating container") - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error creating container"), - }, nil - } - ri, protocol, err := s.checkRef(ctx, statRes.Info) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { @@ -347,14 +339,6 @@ func (s *svc) InitiateFileUpload(ctx context.Context, req *provider.InitiateFile }, nil } - if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - err := errors.New(fmt.Sprintf("gateway: expected reference: got:%+v", statRes.Info)) - log.Err(err).Msg("gateway: error creating container") - return &gateway.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error uploading"), - }, nil - } - ri, protocol, err := s.checkRef(ctx, statRes.Info) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { @@ -558,14 +542,6 @@ func (s *svc) CreateContainer(ctx context.Context, req *provider.CreateContainer }, nil } - if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - err := errors.New(fmt.Sprintf("gateway: expected reference: got:%+v", statRes.Info)) - log.Err(err).Msg("gateway: error creating container") - return &provider.CreateContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error creating container"), - }, nil - } - ri, protocol, err := s.checkRef(ctx, statRes.Info) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { @@ -706,14 +682,6 @@ func (s *svc) Delete(ctx context.Context, req *provider.DeleteRequest) (*provide }, nil } - if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - err := errors.New(fmt.Sprintf("gateway: expected reference: got:%+v", statRes.Info)) - log.Err(err).Msg("gateway: error deleting") - return &provider.DeleteResponse{ - Status: status.NewInternal(ctx, err, "gateway: error deleting"), - }, nil - } - ri, protocol, err := s.checkRef(ctx, statRes.Info) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { @@ -849,14 +817,6 @@ func (s *svc) Move(ctx context.Context, req *provider.MoveRequest) (*provider.Mo }, nil } - if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - err := errors.New(fmt.Sprintf("gateway: expected reference: got:%+v", statRes.Info)) - log.Err(err).Msg("gateway: error deleting") - return &provider.MoveResponse{ - Status: status.NewInternal(ctx, err, "gateway: error deleting"), - }, nil - } - ri, protocol, err := s.checkRef(ctx, statRes.Info) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { @@ -1052,10 +1012,6 @@ func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.St }, nil } - if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - panic("gateway: a share name must be of type reference: ref:" + statRes.Info.Path) - } - ri, protocol, err := s.checkRef(ctx, statRes.Info) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { @@ -1320,9 +1276,8 @@ func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequ if protocol == "webdav" { info, err = s.webdavRefStat(ctx, ref.Target) if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error resolving webdav reference: "+ref.Target), - }, nil + // Might be the case that the webdav token has expired. In that case, use the reference's info + info = ref } } diff --git a/internal/grpc/services/gateway/webdavstorageprovider.go b/internal/grpc/services/gateway/webdavstorageprovider.go index 5c27014571..161ed3e069 100644 --- a/internal/grpc/services/gateway/webdavstorageprovider.go +++ b/internal/grpc/services/gateway/webdavstorageprovider.go @@ -25,6 +25,7 @@ import ( "path" "strings" + ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/errtypes" @@ -45,18 +46,23 @@ func (s *svc) webdavRefStat(ctx context.Context, targetURL string, nameQueries . return nil, err } - ep, err := extractEndpointInfo(targetURL) + ep, err := s.extractEndpointInfo(ctx, targetURL) if err != nil { return nil, err } - c := gowebdav.NewClient(ep.endpoint, "", "") + webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) + if err != nil { + return nil, err + } + + c := gowebdav.NewClient(webdavEP, "", "") c.SetHeader(token.TokenHeader, ep.token) // TODO(ishank011): We need to call PROPFIND ourselves as we need to retrieve // ownloud-specific fields to get the resource ID and permissions. info, err := c.Stat(ep.filePath) if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("gateway: error statting %s at the webdav endpoint: %s", ep.filePath, ep.endpoint)) + return nil, errors.Wrap(err, fmt.Sprintf("gateway: error statting %s at the webdav endpoint: %s", ep.filePath, webdavEP)) } return normalize(info.(*gowebdav.File)), nil } @@ -67,18 +73,23 @@ func (s *svc) webdavRefLs(ctx context.Context, targetURL string, nameQueries ... return nil, err } - ep, err := extractEndpointInfo(targetURL) + ep, err := s.extractEndpointInfo(ctx, targetURL) if err != nil { return nil, err } - c := gowebdav.NewClient(ep.endpoint, "", "") + webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) + if err != nil { + return nil, err + } + + c := gowebdav.NewClient(webdavEP, "", "") c.SetHeader(token.TokenHeader, ep.token) // TODO(ishank011): We need to call PROPFIND ourselves as we need to retrieve // ownloud-specific fields to get the resource ID and permissions. infos, err := c.ReadDir(ep.filePath) if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("gateway: error listing %s at the webdav endpoint: %s", ep.filePath, ep.endpoint)) + return nil, errors.Wrap(err, fmt.Sprintf("gateway: error listing %s at the webdav endpoint: %s", ep.filePath, webdavEP)) } mds := []*provider.ResourceInfo{} @@ -95,16 +106,21 @@ func (s *svc) webdavRefMkdir(ctx context.Context, targetURL string, nameQueries return err } - ep, err := extractEndpointInfo(targetURL) + ep, err := s.extractEndpointInfo(ctx, targetURL) if err != nil { return err } - c := gowebdav.NewClient(ep.endpoint, "", "") + webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) + if err != nil { + return err + } + + c := gowebdav.NewClient(webdavEP, "", "") c.SetHeader(token.TokenHeader, ep.token) err = c.Mkdir(ep.filePath, 0700) if err != nil { - return errors.Wrap(err, fmt.Sprintf("gateway: error creating dir %s at the webdav endpoint: %s", ep.filePath, ep.endpoint)) + return errors.Wrap(err, fmt.Sprintf("gateway: error creating dir %s at the webdav endpoint: %s", ep.filePath, webdavEP)) } return nil } @@ -114,7 +130,11 @@ func (s *svc) webdavRefMove(ctx context.Context, targetURL, src, destination str if err != nil { return err } - srcEP, err := extractEndpointInfo(srcURL) + srcEP, err := s.extractEndpointInfo(ctx, srcURL) + if err != nil { + return err + } + srcWebdavEP, err := s.getWebdavEndpoint(ctx, srcEP.endpoint) if err != nil { return err } @@ -123,17 +143,17 @@ func (s *svc) webdavRefMove(ctx context.Context, targetURL, src, destination str if err != nil { return err } - destEP, err := extractEndpointInfo(destURL) + destEP, err := s.extractEndpointInfo(ctx, destURL) if err != nil { return err } - c := gowebdav.NewClient(srcEP.endpoint, "", "") + c := gowebdav.NewClient(srcWebdavEP, "", "") c.SetHeader(token.TokenHeader, srcEP.token) err = c.Rename(srcEP.filePath, destEP.filePath, true) if err != nil { - return errors.Wrap(err, fmt.Sprintf("gateway: error renaming %s to %s at the webdav endpoint: %s", srcEP.filePath, destEP.filePath, srcEP.endpoint)) + return errors.Wrap(err, fmt.Sprintf("gateway: error renaming %s to %s at the webdav endpoint: %s", srcEP.filePath, destEP.filePath, srcWebdavEP)) } return nil } @@ -144,16 +164,21 @@ func (s *svc) webdavRefDelete(ctx context.Context, targetURL string, nameQueries return err } - ep, err := extractEndpointInfo(targetURL) + ep, err := s.extractEndpointInfo(ctx, targetURL) if err != nil { return err } - c := gowebdav.NewClient(ep.endpoint, "", "") + webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) + if err != nil { + return err + } + + c := gowebdav.NewClient(webdavEP, "", "") c.SetHeader(token.TokenHeader, ep.token) err = c.Remove(ep.filePath) if err != nil { - return errors.Wrap(err, fmt.Sprintf("gateway: error removing %s at the webdav endpoint: %s", ep.filePath, ep.endpoint)) + return errors.Wrap(err, fmt.Sprintf("gateway: error removing %s at the webdav endpoint: %s", ep.filePath, webdavEP)) } return nil } @@ -164,12 +189,16 @@ func (s *svc) webdavRefTransferEndpoint(ctx context.Context, targetURL string, n return "", nil, err } - ep, err := extractEndpointInfo(targetURL) + ep, err := s.extractEndpointInfo(ctx, targetURL) + if err != nil { + return "", nil, err + } + webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) if err != nil { return "", nil, err } - return ep.endpoint, &types.Opaque{ + return webdavEP, &types.Opaque{ Map: map[string]*types.OpaqueEntry{ "webdav-file-path": { Decoder: "plain", @@ -183,21 +212,7 @@ func (s *svc) webdavRefTransferEndpoint(ctx context.Context, targetURL string, n }, nil } -func normalize(info *gowebdav.File) *provider.ResourceInfo { - return &provider.ResourceInfo{ - // TODO(ishank011): Add Id, PermissionSet, Owner - Path: info.Path(), - Type: getResourceType(info.IsDir()), - Etag: info.ETag(), - MimeType: info.ContentType(), - Size: uint64(info.Size()), - Mtime: &types.Timestamp{ - Seconds: uint64(info.ModTime().Unix()), - }, - } -} - -func extractEndpointInfo(targetURL string) (*webdavEndpoint, error) { +func (s *svc) extractEndpointInfo(ctx context.Context, targetURL string) (*webdavEndpoint, error) { if targetURL == "" { return nil, errors.New("gateway: ref target is an empty uri") } @@ -210,11 +225,6 @@ func extractEndpointInfo(targetURL string) (*webdavEndpoint, error) { return nil, errtypes.NotSupported("ref target does not have the webdav scheme") } - parts := strings.SplitN(uri.Opaque, "@", 2) - if len(parts) < 2 { - err := errors.New("gateway: webdav ref does not follow the layout token@webdav_endpoint?name " + targetURL) - return nil, err - } m, err := url.ParseQuery(uri.RawQuery) if err != nil { return nil, errors.Wrap(err, "gateway: error parsing target resource name") @@ -222,11 +232,40 @@ func extractEndpointInfo(targetURL string) (*webdavEndpoint, error) { return &webdavEndpoint{ filePath: m["name"][0], - endpoint: parts[1], - token: parts[0], + endpoint: uri.Host, + token: uri.User.String(), }, nil } +func (s *svc) getWebdavEndpoint(ctx context.Context, domain string) (string, error) { + meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ + Domain: domain, + }) + if err != nil { + return "", errors.Wrap(err, "gateway: error calling GetInfoByDomain") + } + for _, s := range meshProvider.ProviderInfo.Services { + if strings.ToLower(s.Endpoint.Type.Name) == "webdav" { + return s.Endpoint.Path, nil + } + } + return "", errtypes.NotFound(domain) +} + +func normalize(info *gowebdav.File) *provider.ResourceInfo { + return &provider.ResourceInfo{ + // TODO(ishank011): Add Id, PermissionSet, Owner + Path: info.Path(), + Type: getResourceType(info.IsDir()), + Etag: info.ETag(), + MimeType: info.ContentType(), + Size: uint64(info.Size()), + Mtime: &types.Timestamp{ + Seconds: uint64(info.ModTime().Unix()), + }, + } +} + func getResourceType(isDir bool) provider.ResourceType { if isDir { return provider.ResourceType_RESOURCE_TYPE_CONTAINER diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 845f7a0e71..321706f7a2 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -275,7 +275,7 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate } if newRef.GetPath() == "/" { return &provider.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, errors.New("can't upload to mount path"), ""), + Status: status.NewInternal(ctx, errors.New("can't upload to mount path"), "can't upload to mount path"), }, nil } url := *s.dataServerURL @@ -401,6 +401,11 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro Status: status.NewInternal(ctx, err, "error unwrapping path"), }, nil } + if newRef.GetPath() == "/" { + return &provider.DeleteResponse{ + Status: status.NewInternal(ctx, errors.New("can't delete mount path"), "can't delete mount path"), + }, nil + } if err := s.storage.Delete(ctx, newRef); err != nil { var st *rpc.Status