From 7979cadac2019660eac1fca009243b739bdb44da Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Fri, 25 Feb 2022 13:49:29 +0100 Subject: [PATCH] Refactor ctx user agent interceptor and add desktop client ns --- .../grpc/services/gateway/storageprovider.go | 30 ++++---- internal/http/interceptors/auth/auth.go | 4 +- .../http/services/owncloud/ocdav/ocdav.go | 6 ++ pkg/ctx/agentctx.go | 53 ++++++++++++- .../agentctx_test.go} | 73 ++++-------------- pkg/useragent/useragent.go | 77 ------------------- 6 files changed, 92 insertions(+), 151 deletions(-) rename pkg/{useragent/useragent_test.go => ctx/agentctx_test.go} (50%) delete mode 100644 pkg/useragent/useragent.go diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 54c0230f9b..912fc72d1b 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -37,8 +37,6 @@ 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" - ua "github.com/mileusna/useragent" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" @@ -1778,30 +1776,36 @@ func (s *svc) listSharesFolder(ctx context.Context) (*provider.ListContainerResp return lcr, nil } -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/interceptors/auth/auth.go b/internal/http/interceptors/auth/auth.go index c73b8afacb..cb4c55c6a0 100644 --- a/internal/http/interceptors/auth/auth.go +++ b/internal/http/interceptors/auth/auth.go @@ -173,7 +173,7 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err isUnprotectedEndpoint = true } - ctx, err := authenticateUser(w, r, conf, unprotected, tokenStrategy, tokenManager, tokenWriter, credChain, isUnprotectedEndpoint) + ctx, err := authenticateUser(w, r, conf, tokenStrategy, tokenManager, tokenWriter, credChain, isUnprotectedEndpoint) if err != nil { if !isUnprotectedEndpoint { return @@ -187,7 +187,7 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err return chain, nil } -func authenticateUser(w http.ResponseWriter, r *http.Request, conf *config, unprotected []string, tokenStrategy auth.TokenStrategy, tokenManager token.Manager, tokenWriter auth.TokenWriter, credChain map[string]auth.CredentialStrategy, isUnprotectedEndpoint bool) (context.Context, error) { +func authenticateUser(w http.ResponseWriter, r *http.Request, conf *config, tokenStrategy auth.TokenStrategy, tokenManager token.Manager, tokenWriter auth.TokenWriter, credChain map[string]auth.CredentialStrategy, isUnprotectedEndpoint bool) (context.Context, error) { ctx := r.Context() log := appctx.GetLogger(ctx) diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index e2aa3c0ca5..6d3cadc0f1 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -203,6 +203,12 @@ func (s *svc) Handler() http.Handler { head, r.URL.Path = router.ShiftPath(r.URL.Path) log.Debug().Str("head", head).Str("tail", r.URL.Path).Msg("http routing") switch head { + case "cernbox": + var origin string + origin, r.URL.Path = router.ShiftPath(r.URL.Path) // desktop or mobile + _, r.URL.Path = router.ShiftPath(r.URL.Path) // remote.php + head, r.URL.Path = router.ShiftPath(r.URL.Path) + base = path.Join(base, "cernbox", origin, "remote.php") case "status.php": s.doStatus(w, r) return diff --git a/pkg/ctx/agentctx.go b/pkg/ctx/agentctx.go index 74eda0b563..1d96f355af 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 c37ae97508..8cfd6b0e6a 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 53d766ed26..0000000000 --- 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 -}