diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index bfb19945051..76e05a6a0e4 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -37,9 +37,7 @@ import ( types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ctxpkg "github.com/cs3org/reva/pkg/ctx" rtrace "github.com/cs3org/reva/pkg/trace" - "github.com/cs3org/reva/pkg/useragent" "github.com/cs3org/reva/pkg/utils" - ua "github.com/mileusna/useragent" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" @@ -1602,30 +1600,36 @@ func (s *svc) ListContainerStream(_ *provider.ListContainerStreamRequest, _ gate return errtypes.NotSupported("Unimplemented") } -func (s *svc) isPathAllowed(ua *ua.UserAgent, path string) bool { - uaLst, ok := s.c.AllowedUserAgents[path] - if !ok { - // if no user agent is defined for a path, all user agents are allowed - return true - } - return useragent.IsUserAgentAllowed(ua, uaLst) -} - func (s *svc) filterProvidersByUserAgent(ctx context.Context, providers []*registry.ProviderInfo) []*registry.ProviderInfo { - ua, ok := ctxpkg.ContextGetUserAgent(ctx) + cat, ok := ctxpkg.ContextGetUserAgentCategory(ctx) if !ok { return providers } filters := []*registry.ProviderInfo{} for _, p := range providers { - if s.isPathAllowed(ua, p.ProviderPath) { + if s.isPathAllowed(cat, p.ProviderPath) { filters = append(filters, p) } } return filters } +func (s *svc) isPathAllowed(cat string, path string) bool { + allowedUserAgents, ok := s.c.AllowedUserAgents[path] + if !ok { + // if no user agent is defined for a path, all user agents are allowed + return true + } + + for _, userAgent := range allowedUserAgents { + if userAgent == cat { + return true + } + } + return false +} + func (s *svc) listContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { providers, err := s.findProviders(ctx, req.Ref) if err != nil { diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index 0f51ef2a82b..85e73605228 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -105,6 +105,7 @@ type Config struct { PublicURL string `mapstructure:"public_url"` FavoriteStorageDriver string `mapstructure:"favorite_storage_driver"` FavoriteStorageDrivers map[string]map[string]interface{} `mapstructure:"favorite_storage_drivers"` + DesktopClientNamespace string `mapstructure:"desktop_client_namespace"` } func (c *Config) init() { diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 4cc88ef9708..1d446c2eddb 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -491,6 +491,9 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide md.Path = strings.TrimPrefix(md.Path, ns) baseURI := ctx.Value(ctxKeyBaseURI).(string) + if userAgent, ok := ctxpkg.ContextGetUserAgentCategory(ctx); ok && userAgent == ctxpkg.DesktopUserAgent { + baseURI = path.Join(s.c.DesktopClientNamespace, baseURI) + } ref := path.Join(baseURI, md.Path) if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { diff --git a/pkg/ctx/agentctx.go b/pkg/ctx/agentctx.go index 74eda0b5635..1d96f355afa 100644 --- a/pkg/ctx/agentctx.go +++ b/pkg/ctx/agentctx.go @@ -20,13 +20,21 @@ package ctx import ( "context" + "strings" ua "github.com/mileusna/useragent" "google.golang.org/grpc/metadata" ) // UserAgentHeader is the header used for the user agent -const UserAgentHeader = "x-user-agent" +const ( + UserAgentHeader = "x-user-agent" + + WebUserAgent = "web" + GrpcUserAgent = "grpc" + MobileUserAgent = "mobile" + DesktopUserAgent = "desktop" +) // ContextGetUserAgent returns the user agent if set in the given context. // see https://github.com/grpc/grpc-go/issues/1100 @@ -56,3 +64,46 @@ func ContextGetUserAgentString(ctx context.Context) (string, bool) { } return userAgentLst[0], true } + +// ContextGetUserAgentCategory returns the category of the user agent +// (i.e. if it is a web, mobile, desktop or grpc user agent) +func ContextGetUserAgentCategory(ctx context.Context) (string, bool) { + agent, ok := ContextGetUserAgent(ctx) + if !ok { + return "", false + } + switch { + case isWeb(agent): + return WebUserAgent, true + case isMobile(agent): + return MobileUserAgent, true + case isDesktop(agent): + return DesktopUserAgent, true + case isGRPC(agent): + return GrpcUserAgent, true + default: + return "", false + } +} + +func isWeb(ua *ua.UserAgent) bool { + return ua.IsChrome() || ua.IsEdge() || ua.IsFirefox() || ua.IsSafari() || + ua.IsInternetExplorer() || ua.IsOpera() || ua.IsOperaMini() +} + +// isMobile returns true if the useragent is generated by the mobile +func isMobile(ua *ua.UserAgent) bool { + // workaround as the library does not recognise iOS string inside the user agent + isIOS := ua.IsIOS() || strings.Contains(ua.String, "iOS") + return !isWeb(ua) && (ua.IsAndroid() || isIOS) +} + +// isDesktop returns true if the useragent is generated by a desktop application +func isDesktop(ua *ua.UserAgent) bool { + return ua.Desktop && !isWeb(ua) +} + +// isGRPC returns true if the useragent is generated by a grpc client +func isGRPC(ua *ua.UserAgent) bool { + return strings.HasPrefix(ua.Name, "grpc") +} diff --git a/pkg/useragent/useragent_test.go b/pkg/ctx/agentctx_test.go similarity index 50% rename from pkg/useragent/useragent_test.go rename to pkg/ctx/agentctx_test.go index c37ae97508a..8cfd6b0e6ad 100644 --- a/pkg/useragent/useragent_test.go +++ b/pkg/ctx/agentctx_test.go @@ -16,12 +16,13 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package useragent +package ctx import ( + "context" "testing" - ua "github.com/mileusna/useragent" + "google.golang.org/grpc/metadata" ) func TestUserAgentIsAllowed(t *testing.T) { @@ -29,92 +30,48 @@ func TestUserAgentIsAllowed(t *testing.T) { tests := []struct { description string userAgent string - userAgents []string - expected bool + expected string }{ { description: "grpc-go", userAgent: "grpc-go", - userAgents: []string{"grpc"}, - expected: true, - }, - { - description: "grpc-go", - userAgent: "grpc-go", - userAgents: []string{"desktop", "mobile", "web"}, - expected: false, + expected: "grpc", }, { description: "web-firefox", userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15", - userAgents: []string{"web"}, - expected: true, - }, - { - description: "web-firefox", - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15", - userAgents: []string{"desktop", "mobile", "grpc"}, - expected: false, - }, - { - description: "desktop-mirall", - userAgent: "Mozilla/5.0 (Linux) mirall/2.7.1 (build 2596) (cernboxcmd, centos-3.10.0-1160.36.2.el7.x86_64 ClientArchitecture: x86_64 OsArchitecture: x86_64)", - userAgents: []string{"desktop"}, - expected: true, + expected: "web", }, { description: "desktop-mirall", userAgent: "Mozilla/5.0 (Linux) mirall/2.7.1 (build 2596) (cernboxcmd, centos-3.10.0-1160.36.2.el7.x86_64 ClientArchitecture: x86_64 OsArchitecture: x86_64)", - userAgents: []string{"web", "mobile", "grpc"}, - expected: false, - }, - { - description: "mobile-android", - userAgent: "Mozilla/5.0 (Android) ownCloud-android/2.13.1 cernbox/Android", - userAgents: []string{"mobile"}, - expected: true, - }, - { - description: "mobile-ios", - userAgent: "Mozilla/5.0 (iOS) ownCloud-iOS/3.8.0 cernbox/iOS", - userAgents: []string{"mobile"}, - expected: true, + expected: "desktop", }, { description: "mobile-android", userAgent: "Mozilla/5.0 (Android) ownCloud-android/2.13.1 cernbox/Android", - userAgents: []string{"web", "desktop", "grpc"}, - expected: false, + expected: "mobile", }, { description: "mobile-ios", userAgent: "Mozilla/5.0 (iOS) ownCloud-iOS/3.8.0 cernbox/iOS", - userAgents: []string{"web", "desktop", "grpc"}, - expected: false, + expected: "mobile", }, { description: "mobile-web", userAgent: "Mozilla/5.0 (Android 11; Mobile; rv:86.0) Gecko/86.0 Firefox/86.0", - userAgents: []string{"web"}, - expected: true, - }, - { - description: "mobile-web", - userAgent: "Mozilla/5.0 (Android 11; Mobile; rv:86.0) Gecko/86.0 Firefox/86.0", - userAgents: []string{"desktop", "grpc", "mobile"}, - expected: false, + expected: "web", }, } + ctx := context.Background() for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { + ctx = metadata.NewIncomingContext(ctx, metadata.New(map[string]string{UserAgentHeader: tt.userAgent})) + cat, _ := ContextGetUserAgentCategory(ctx) - ua := ua.Parse(tt.userAgent) - - res := IsUserAgentAllowed(&ua, tt.userAgents) - - if res != tt.expected { - t.Fatalf("result does not match with expected. got=%+v expected=%+v", res, tt.expected) + if cat != tt.expected { + t.Fatalf("result does not match with expected. got=%+v expected=%+v", cat, tt.expected) } }) diff --git a/pkg/useragent/useragent.go b/pkg/useragent/useragent.go deleted file mode 100644 index 53d766ed265..00000000000 --- a/pkg/useragent/useragent.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package useragent - -import ( - "strings" - - ua "github.com/mileusna/useragent" -) - -// isWeb returns true if the useragent is generated by the web -func isWeb(ua *ua.UserAgent) bool { - return ua.IsChrome() || ua.IsEdge() || ua.IsFirefox() || ua.IsSafari() || - ua.IsInternetExplorer() || ua.IsOpera() || ua.IsOperaMini() -} - -// isMobile returns true if the useragent is generated by the mobile -func isMobile(ua *ua.UserAgent) bool { - // workaround as the library does not recognise iOS string inside the user agent - isIOS := ua.IsIOS() || strings.Contains(ua.String, "iOS") - return !isWeb(ua) && (ua.IsAndroid() || isIOS) -} - -// isDesktop returns true if the useragent is generated by a desktop application -func isDesktop(ua *ua.UserAgent) bool { - return ua.Desktop && !isWeb(ua) -} - -// isGRPC returns true if the useragent is generated by a grpc client -func isGRPC(ua *ua.UserAgent) bool { - return strings.HasPrefix(ua.Name, "grpc") -} - -// getCategory returns the category of the user agent -// (i.e. if it is a web, mobile, desktop or grpc user agent) -func getCategory(ua *ua.UserAgent) string { - switch { - case isWeb(ua): - return "web" - case isMobile(ua): - return "mobile" - case isDesktop(ua): - return "desktop" - case isGRPC(ua): - return "grpc" - default: - return "" - } -} - -// IsUserAgentAllowed return true if the user agent corresponds -// to one in the allowed user agents list -func IsUserAgentAllowed(ua *ua.UserAgent, allowedUserAgents []string) bool { - cat := getCategory(ua) - for _, userAgent := range allowedUserAgents { - if userAgent == cat { - return true - } - } - return false -}