Skip to content

Commit

Permalink
Filter root path according to the agent that makes the request (#2212)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmgigi96 authored Nov 17, 2021
1 parent 427e944 commit 7aabd53
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 69 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/clean-root-path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Filter root path according to the agent that makes the request

https://github.com/cs3org/reva/pull/2212
25 changes: 10 additions & 15 deletions internal/grpc/interceptors/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import (
"time"

"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/rs/zerolog"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
)
Expand All @@ -40,16 +40,13 @@ func NewUnary() grpc.UnaryServerInterceptor {
code := status.Code(err)
end := time.Now()
diff := end.Sub(start).Nanoseconds()
var fromAddress, userAgent string
var fromAddress string
if p, ok := peer.FromContext(ctx); ok {
fromAddress = p.Addr.Network() + "://" + p.Addr.String()
}
if md, ok := metadata.FromIncomingContext(ctx); ok {
if vals, ok := md["user-agent"]; ok {
if len(vals) > 0 && vals[0] != "" {
userAgent = vals[0]
}
}
userAgent, ok := ctxpkg.ContextGetUserAgentString(ctx)
if !ok {
userAgent = ""
}

log := appctx.GetLogger(ctx)
Expand Down Expand Up @@ -77,21 +74,19 @@ func NewUnary() grpc.UnaryServerInterceptor {
// that adds trace information to the request.
func NewStream() grpc.StreamServerInterceptor {
interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
start := time.Now()
err := handler(srv, ss)
end := time.Now()
code := status.Code(err)
diff := end.Sub(start).Nanoseconds()
var fromAddress, userAgent string
var fromAddress string
if p, ok := peer.FromContext(ss.Context()); ok {
fromAddress = p.Addr.Network() + "://" + p.Addr.String()
}
if md, ok := metadata.FromIncomingContext(ss.Context()); ok {
if vals, ok := md["user-agent"]; ok {
if len(vals) > 0 && vals[0] != "" {
userAgent = vals[0]
}
}
userAgent, ok := ctxpkg.ContextGetUserAgentString(ctx)
if !ok {
userAgent = ""
}

log := appctx.GetLogger(ss.Context())
Expand Down
70 changes: 70 additions & 0 deletions internal/grpc/interceptors/useragent/useragent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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 (
"context"

ctxpkg "github.com/cs3org/reva/pkg/ctx"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)

// NewUnary returns a new unary interceptor that adds
// the useragent to the context.
func NewUnary() grpc.UnaryServerInterceptor {
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
if lst, ok := md[ctxpkg.UserAgentHeader]; ok && len(lst) != 0 {
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.UserAgentHeader, lst[0])
}
}
return handler(ctx, req)
}
return interceptor
}

// NewStream returns a new server stream interceptor
// that adds the user agent to the context.
func NewStream() grpc.StreamServerInterceptor {
interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
if md, ok := metadata.FromIncomingContext(ctx); ok {
if lst, ok := md[ctxpkg.UserAgentHeader]; ok && len(lst) != 0 {
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.UserAgentHeader, lst[0])
}
}
wrapped := newWrappedServerStream(ctx, ss)
return handler(srv, wrapped)
}
return interceptor
}

func newWrappedServerStream(ctx context.Context, ss grpc.ServerStream) *wrappedServerStream {
return &wrappedServerStream{ServerStream: ss, newCtx: ctx}
}

type wrappedServerStream struct {
grpc.ServerStream
newCtx context.Context
}

func (ss *wrappedServerStream) Context() context.Context {
return ss.newCtx
}
1 change: 1 addition & 0 deletions internal/grpc/services/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type config struct {
HomeMapping string `mapstructure:"home_mapping"`
TokenManagers map[string]map[string]interface{} `mapstructure:"token_managers"`
EtagCacheTTL int `mapstructure:"etag_cache_ttl"`
AllowedUserAgents map[string][]string `mapstructure:"allowed_user_agents"` // map[path][]user-agent
CreateHomeCacheTTL int `mapstructure:"create_home_cache_ttl"`
}

Expand Down
42 changes: 35 additions & 7 deletions internal/grpc/services/gateway/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ import (
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
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"
Expand Down Expand Up @@ -1676,6 +1679,30 @@ 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)
if !ok {
return providers
}

filters := []*registry.ProviderInfo{}
for _, p := range providers {
if s.isPathAllowed(ua, p.ProviderPath) {
filters = append(filters, p)
}
}
return filters
}

func (s *svc) listContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) {
providers, err := s.findProviders(ctx, req.Ref)
if err != nil {
Expand All @@ -1686,6 +1713,7 @@ func (s *svc) listContainer(ctx context.Context, req *provider.ListContainerRequ
providers = getUniqueProviders(providers)

resPath := req.Ref.GetPath()

if len(providers) == 1 && (utils.IsRelativeReference(req.Ref) || resPath == "" || strings.HasPrefix(resPath, providers[0].ProviderPath)) {
c, err := s.getStorageProviderClient(ctx, providers[0])
if err != nil {
Expand All @@ -1707,7 +1735,7 @@ func (s *svc) listContainerAcrossProviders(ctx context.Context, req *provider.Li
nestedInfos := make(map[string]*provider.ResourceInfo)
log := appctx.GetLogger(ctx)

for _, p := range providers {
for _, p := range s.filterProvidersByUserAgent(ctx, providers) {
c, err := s.getStorageProviderClient(ctx, p)
if err != nil {
log.Err(err).Msg("error connecting to storage provider=" + p.Address)
Expand Down Expand Up @@ -2201,15 +2229,15 @@ func (s *svc) findProviders(ctx context.Context, ref *provider.Reference) ([]*re
}

func getUniqueProviders(providers []*registry.ProviderInfo) []*registry.ProviderInfo {
unique := make(map[string]bool)
unique := make(map[string]*registry.ProviderInfo)
for _, p := range providers {
unique[p.Address] = true
unique[p.Address] = p
}
p := make([]*registry.ProviderInfo, 0, len(unique))
for addr := range unique {
p = append(p, &registry.ProviderInfo{Address: addr})
res := make([]*registry.ProviderInfo, 0, len(unique))
for _, provider := range unique {
res = append(res, provider)
}
return p
return res
}

type etagWithTS struct {
Expand Down
2 changes: 2 additions & 0 deletions internal/http/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ func New(m map[string]interface{}, unprotected []string) (global.Middleware, err
ctx = ctxpkg.ContextSetToken(ctx, tkn)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, tkn) // TODO(jfd): hardcoded metadata key. use PerRPCCredentials?

ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.UserAgentHeader, r.UserAgent())

r = r.WithContext(ctx)
h.ServeHTTP(w, r)
})
Expand Down
26 changes: 20 additions & 6 deletions pkg/ctx/agentctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,34 @@ import (
"google.golang.org/grpc/metadata"
)

// UserAgentHeader is the header used for the user agent
const UserAgentHeader = "x-user-agent"

// 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) {
if userAgentStr, ok := ContextGetUserAgentString(ctx); ok {
userAgent := ua.Parse(userAgentStr)
return &userAgent, true
}
return nil, false
}

// ContextGetUserAgentString returns the user agent string if set in the given context.
func ContextGetUserAgentString(ctx context.Context) (string, bool) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, false
return "", false
}
userAgentLst, ok := md["user-agent"]
userAgentLst, ok := md[UserAgentHeader]
if !ok {
return nil, false
userAgentLst, ok = md["user-agent"]
if !ok {
return "", false
}
}
if len(userAgentLst) == 0 {
return nil, false
return "", false
}
userAgent := ua.Parse(userAgentLst[0])
return &userAgent, true
return userAgentLst[0], true
}
3 changes: 3 additions & 0 deletions pkg/rgrpc/rgrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/cs3org/reva/internal/grpc/interceptors/log"
"github.com/cs3org/reva/internal/grpc/interceptors/recovery"
"github.com/cs3org/reva/internal/grpc/interceptors/token"
"github.com/cs3org/reva/internal/grpc/interceptors/useragent"
"github.com/cs3org/reva/pkg/sharedconf"
rtrace "github.com/cs3org/reva/pkg/trace"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
Expand Down Expand Up @@ -290,6 +291,7 @@ func (s *Server) getInterceptors(unprotected []string) ([]grpc.ServerOption, err
unaryInterceptors = append([]grpc.UnaryServerInterceptor{
appctx.NewUnary(s.log),
token.NewUnary(),
useragent.NewUnary(),
log.NewUnary(),
recovery.NewUnary(),
}, unaryInterceptors...)
Expand Down Expand Up @@ -331,6 +333,7 @@ func (s *Server) getInterceptors(unprotected []string) ([]grpc.ServerOption, err
authStream,
appctx.NewStream(s.log),
token.NewStream(),
useragent.NewStream(),
log.NewStream(),
recovery.NewStream(),
}, streamInterceptors...)
Expand Down
44 changes: 3 additions & 41 deletions pkg/storage/registry/static/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ 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"
)
Expand All @@ -44,10 +43,9 @@ func init() {
var bracketRegex = regexp.MustCompile(`\[(.*?)\]`)

type rule struct {
Mapping string `mapstructure:"mapping"`
Address string `mapstructure:"address"`
Aliases map[string]string `mapstructure:"aliases"`
AllowedUserAgents []string `mapstructure:"allowed_user_agents"`
Mapping string `mapstructure:"mapping"`
Address string `mapstructure:"address"`
Aliases map[string]string `mapstructure:"aliases"`
}

type config struct {
Expand Down Expand Up @@ -140,27 +138,6 @@ 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
Expand Down Expand Up @@ -197,21 +174,6 @@ func (b *reg) FindProviders(ctx context.Context, ref *provider.Reference) ([]*re
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 {
Expand Down
Loading

0 comments on commit 7aabd53

Please sign in to comment.