From 1be4a24e9b009c0397b02a402b9938146f52ef0a Mon Sep 17 00:00:00 2001
From: Gianmaria Del Monte <39946305+gmgigi96@users.noreply.github.com>
Date: Mon, 25 Oct 2021 07:37:36 +0200
Subject: [PATCH] Filter root paths according to user agent (#2193)

---
 changelog/unreleased/storage-ua-filtering.md |  7 +++
 pkg/ctx/agentctx.go                          | 44 +++++++++++++++++++
 pkg/storage/registry/static/static.go        | 45 ++++++++++++++++++--
 3 files changed, 93 insertions(+), 3 deletions(-)
 create mode 100644 changelog/unreleased/storage-ua-filtering.md
 create mode 100644 pkg/ctx/agentctx.go

diff --git a/changelog/unreleased/storage-ua-filtering.md b/changelog/unreleased/storage-ua-filtering.md
new file mode 100644
index 0000000000..61e7731a7a
--- /dev/null
+++ b/changelog/unreleased/storage-ua-filtering.md
@@ -0,0 +1,7 @@
+Enhancement: Filter root paths according to user agent
+
+Adds a new rule setting in the storage registry ("allowed_user_agents"),
+that allows a user to specify which storage provider shows according
+to the user agent that made the request.
+
+https://github.com/cs3org/reva/pull/2193
diff --git a/pkg/ctx/agentctx.go b/pkg/ctx/agentctx.go
new file mode 100644
index 0000000000..6b361676e1
--- /dev/null
+++ b/pkg/ctx/agentctx.go
@@ -0,0 +1,44 @@
+// 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 ctx
+
+import (
+	"context"
+
+	ua "github.com/mileusna/useragent"
+	"google.golang.org/grpc/metadata"
+)
+
+// ContextGetUserAgent returns the user agent if set in the given context.
+// see https://github.com/grpc/grpc-go/issues/1100
+func ContextGetUserAgent(ctx context.Context) (*ua.UserAgent, bool) {
+	md, ok := metadata.FromIncomingContext(ctx)
+	if !ok {
+		return nil, false
+	}
+	userAgentLst, ok := md["user-agent"]
+	if !ok {
+		return nil, false
+	}
+	if len(userAgentLst) == 0 {
+		return nil, false
+	}
+	userAgent := ua.Parse(userAgentLst[0])
+	return &userAgent, true
+}
diff --git a/pkg/storage/registry/static/static.go b/pkg/storage/registry/static/static.go
index 58d3845013..d5d84403e4 100644
--- a/pkg/storage/registry/static/static.go
+++ b/pkg/storage/registry/static/static.go
@@ -32,6 +32,7 @@ import (
 	"github.com/cs3org/reva/pkg/storage"
 	"github.com/cs3org/reva/pkg/storage/registry/registry"
 	"github.com/cs3org/reva/pkg/storage/utils/templates"
+	ua "github.com/mileusna/useragent"
 	"github.com/mitchellh/mapstructure"
 	"github.com/pkg/errors"
 )
@@ -43,9 +44,10 @@ func init() {
 var bracketRegex = regexp.MustCompile(`\[(.*?)\]`)
 
 type rule struct {
-	Mapping string            `mapstructure:"mapping"`
-	Address string            `mapstructure:"address"`
-	Aliases map[string]string `mapstructure:"aliases"`
+	Mapping           string            `mapstructure:"mapping"`
+	Address           string            `mapstructure:"address"`
+	Aliases           map[string]string `mapstructure:"aliases"`
+	AllowedUserAgents []string          `mapstructure:"allowed_user_agents"`
 }
 
 type config struct {
@@ -138,6 +140,27 @@ func (b *reg) GetHome(ctx context.Context) (*registrypb.ProviderInfo, error) {
 	return nil, errors.New("static: home not found")
 }
 
+func userAgentIsAllowed(ua *ua.UserAgent, userAgents []string) bool {
+	for _, userAgent := range userAgents {
+		switch userAgent {
+		case "web":
+			if ua.IsChrome() || ua.IsEdge() || ua.IsFirefox() || ua.IsSafari() ||
+				ua.IsInternetExplorer() || ua.IsOpera() || ua.IsOperaMini() {
+				return true
+			}
+		case "desktop":
+			if ua.Desktop {
+				return true
+			}
+		case "grpc":
+			if strings.HasPrefix(ua.Name, "grpc") {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 func (b *reg) FindProviders(ctx context.Context, ref *provider.Reference) ([]*registrypb.ProviderInfo, error) {
 	// find longest match
 	var match *registrypb.ProviderInfo
@@ -173,6 +196,22 @@ func (b *reg) FindProviders(ctx context.Context, ref *provider.Reference) ([]*re
 	fn := path.Clean(ref.GetPath())
 	if fn != "" {
 		for prefix, rule := range b.c.Rules {
+
+			// check if the provider is allowed to be shown according to the
+			// user agent that made the request
+			// if the list of AllowedUserAgents is empty, this means that
+			// every agents that made the request could see the provider
+
+			if len(rule.AllowedUserAgents) != 0 {
+				ua, ok := ctxpkg.ContextGetUserAgent(ctx)
+				if !ok {
+					continue
+				}
+				if !userAgentIsAllowed(ua, rule.AllowedUserAgents) {
+					continue // skip this provider
+				}
+			}
+
 			addr := getProviderAddr(ctx, rule)
 			r, err := regexp.Compile("^" + prefix)
 			if err != nil {