github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/gateway/appprovider.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package gateway 20 21 import ( 22 "context" 23 "crypto/tls" 24 "fmt" 25 "net/url" 26 "strings" 27 28 providerpb "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" 29 registry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" 30 providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" 31 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 32 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 33 ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" 34 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 35 storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 36 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 37 "github.com/cs3org/reva/v2/pkg/appctx" 38 "github.com/cs3org/reva/v2/pkg/auth/scope" 39 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 40 "github.com/cs3org/reva/v2/pkg/errtypes" 41 "github.com/cs3org/reva/v2/pkg/rgrpc/status" 42 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 43 "github.com/cs3org/reva/v2/pkg/token" 44 "github.com/cs3org/reva/v2/pkg/utils" 45 "github.com/pkg/errors" 46 "google.golang.org/grpc" 47 "google.golang.org/grpc/credentials" 48 "google.golang.org/grpc/credentials/insecure" 49 "google.golang.org/grpc/metadata" 50 ) 51 52 func (s *svc) OpenInApp(ctx context.Context, req *gateway.OpenInAppRequest) (*providerpb.OpenInAppResponse, error) { 53 statRes, err := s.Stat(ctx, &storageprovider.StatRequest{ 54 Ref: req.Ref, 55 }) 56 if err != nil { 57 return &providerpb.OpenInAppResponse{ 58 Status: status.NewInternal(ctx, "gateway: error calling Stat on the resource path for the app provider: "+req.Ref.GetPath()), 59 }, nil 60 } 61 if statRes.Status.Code != rpc.Code_CODE_OK { 62 return &providerpb.OpenInAppResponse{ 63 Status: statRes.Status, 64 }, nil 65 } 66 67 fileInfo := statRes.Info 68 69 // The file is a share 70 if fileInfo.Type == storageprovider.ResourceType_RESOURCE_TYPE_REFERENCE { 71 uri, err := url.Parse(fileInfo.Target) 72 if err != nil { 73 return &providerpb.OpenInAppResponse{ 74 Status: status.NewInternal(ctx, "gateway: error parsing target uri: "+fileInfo.Target), 75 }, nil 76 } 77 if uri.Scheme == "webdav" { 78 insecure, skipVerify := getGRPCConfig(req.Opaque) 79 return s.openFederatedShares(ctx, fileInfo.Target, req.ViewMode, req.App, insecure, skipVerify, "") 80 } 81 82 res, err := s.Stat(ctx, &storageprovider.StatRequest{ 83 Ref: req.Ref, 84 }) 85 if err != nil { 86 return &providerpb.OpenInAppResponse{ 87 Status: status.NewInternal(ctx, "gateway: error calling Stat on the resource path for the app provider: "+req.Ref.GetPath()), 88 }, nil 89 } 90 if res.Status.Code != rpc.Code_CODE_OK { 91 return &providerpb.OpenInAppResponse{ 92 Status: status.NewInternal(ctx, "Stat failed on the resource path for the app provider: "+req.Ref.GetPath()), 93 }, nil 94 } 95 fileInfo = res.Info 96 } 97 return s.openLocalResources(ctx, fileInfo, req.ViewMode, req.App, req.Opaque) 98 } 99 100 func (s *svc) openFederatedShares(ctx context.Context, targetURL string, vm gateway.OpenInAppRequest_ViewMode, app string, 101 insecure, skipVerify bool, nameQueries ...string) (*providerpb.OpenInAppResponse, error) { 102 log := appctx.GetLogger(ctx) 103 targetURL, err := appendNameQuery(targetURL, nameQueries...) 104 if err != nil { 105 return nil, err 106 } 107 ep, err := s.extractEndpointInfo(ctx, targetURL) 108 if err != nil { 109 return nil, err 110 } 111 112 ref := &storageprovider.Reference{Path: ep.filePath} 113 appProviderReq := &gateway.OpenInAppRequest{ 114 Ref: ref, 115 ViewMode: vm, 116 App: app, 117 } 118 119 meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ 120 Domain: ep.endpoint, 121 }) 122 if err != nil { 123 return nil, errors.Wrap(err, "gateway: error calling GetInfoByDomain") 124 } 125 var gatewayEP string 126 for _, s := range meshProvider.ProviderInfo.Services { 127 if strings.ToLower(s.Endpoint.Type.Name) == "gateway" { 128 gatewayEP = s.Endpoint.Path 129 } 130 } 131 log.Debug().Msgf("Forwarding OpenInApp request to: %s", gatewayEP) 132 133 conn, err := getConn(gatewayEP, insecure, skipVerify) 134 if err != nil { 135 log.Err(err).Msg("error connecting to remote reva") 136 return nil, errors.Wrap(err, "gateway: error connecting to remote reva") 137 } 138 139 gatewayClient := gateway.NewGatewayAPIClient(conn) 140 remoteCtx := ctxpkg.ContextSetToken(context.Background(), ep.token) 141 remoteCtx = metadata.AppendToOutgoingContext(remoteCtx, ctxpkg.TokenHeader, ep.token) 142 143 res, err := gatewayClient.OpenInApp(remoteCtx, appProviderReq) 144 if err != nil { 145 log.Err(err).Msg("error returned by remote OpenInApp call") 146 return nil, errors.Wrap(err, "gateway: error calling OpenInApp") 147 } 148 return res, nil 149 } 150 151 func (s *svc) openLocalResources(ctx context.Context, ri *storageprovider.ResourceInfo, 152 vm gateway.OpenInAppRequest_ViewMode, app string, opaque *typespb.Opaque) (*providerpb.OpenInAppResponse, error) { 153 154 accessToken, ok := ctxpkg.ContextGetToken(ctx) 155 if !ok || accessToken == "" { 156 return &providerpb.OpenInAppResponse{ 157 Status: status.NewUnauthenticated(ctx, errtypes.InvalidCredentials("Access token is invalid or empty"), ""), 158 }, nil 159 } 160 161 provider, err := s.findAppProvider(ctx, ri, app) 162 if err != nil { 163 err = errors.Wrap(err, "gateway: error calling findAppProvider") 164 if _, ok := err.(errtypes.IsNotFound); ok { 165 return &providerpb.OpenInAppResponse{ 166 Status: status.NewNotFound(ctx, "Could not find the requested app provider"), 167 }, nil 168 } 169 return nil, err 170 } 171 172 appProviderClient, err := pool.GetAppProviderClient(provider.Address) 173 if err != nil { 174 return nil, errors.Wrap(err, "gateway: error calling GetAppProviderClient") 175 } 176 177 appProviderReq, err := buildOpenInAppRequest(ctx, ri, vm, s.tokenmgr, accessToken, opaque) 178 if err != nil { 179 return nil, errors.Wrap(err, "gateway: error building OpenInApp request") 180 } 181 182 res, err := appProviderClient.OpenInApp(ctx, appProviderReq) 183 if err != nil { 184 return nil, errors.Wrap(err, "gateway: error calling OpenInApp") 185 } 186 187 return res, nil 188 } 189 190 func buildOpenInAppRequest(ctx context.Context, ri *storageprovider.ResourceInfo, vm gateway.OpenInAppRequest_ViewMode, tokenmgr token.Manager, accessToken string, opaque *typespb.Opaque) (*providerpb.OpenInAppRequest, error) { 191 // in case of a view only mode and a stat permission we need to create a view only token 192 if vm == gateway.OpenInAppRequest_VIEW_MODE_VIEW_ONLY && ri.GetPermissionSet().GetStat() && !ri.PermissionSet.GetInitiateFileDownload() { 193 // Limit scope to the resource 194 scope, err := scope.AddResourceInfoScope(ri, providerv1beta1.Role_ROLE_VIEWER, nil) 195 if err != nil { 196 return nil, err 197 } 198 199 // build a fake user object for the token 200 currentuser, ok := ctxpkg.ContextGetUser(ctx) 201 if !ok { 202 return nil, errors.New("user not found in context") 203 } 204 205 scopedUser := &userpb.User{ 206 Id: ri.GetOwner(), // the owner of the resource is always set, right? 207 DisplayName: "View Only user for " + currentuser.GetUsername(), 208 } 209 210 // mint a view only token 211 viewOnlyToken, err := tokenmgr.MintToken(ctx, scopedUser, scope) 212 if err != nil { 213 return nil, err 214 } 215 216 // TODO we should not append the token to the opaque, we should have a dedicated field in the request 217 opaque = utils.AppendPlainToOpaque(opaque, "viewOnlyToken", viewOnlyToken) 218 } 219 220 return &providerpb.OpenInAppRequest{ 221 ResourceInfo: ri, 222 ViewMode: providerpb.ViewMode(vm), 223 AccessToken: accessToken, 224 // ViewOnlyToken: viewOnlyToken // scoped to the shared resource if the stat response hase a ViewOnly permission 225 Opaque: opaque, 226 }, nil 227 } 228 229 func (s *svc) findAppProvider(ctx context.Context, ri *storageprovider.ResourceInfo, app string) (*registry.ProviderInfo, error) { 230 c, err := pool.GetAppRegistryClient(s.c.AppRegistryEndpoint) 231 if err != nil { 232 err = errors.Wrap(err, "gateway: error getting appregistry client") 233 return nil, err 234 } 235 236 // when app is empty it means the user assumes a default behaviour. 237 // From a web perspective, means the user click on the file itself. 238 // Normally the file will get downloaded but if a suitable application exists 239 // the behaviour will change from download to open the file with the app. 240 if app == "" { 241 // If app is empty means that we need to rely on "default" behaviour. 242 // We currently do not have user preferences implemented so the only default 243 // we can currently enforce is one configured by the system admins, the 244 // "system default". 245 // If a default is not set we raise an error rather that giving the user the first provider in the list 246 // as the list is built on init time and is not deterministic, giving the user different results on service 247 // reload. 248 res, err := c.GetDefaultAppProviderForMimeType(ctx, ®istry.GetDefaultAppProviderForMimeTypeRequest{ 249 MimeType: ri.MimeType, 250 }) 251 if err != nil { 252 err = errors.Wrap(err, "gateway: error calling GetDefaultAppProviderForMimeType") 253 return nil, err 254 255 } 256 257 // we've found a provider 258 if res.Status.Code == rpc.Code_CODE_OK && res.Provider != nil { 259 return res.Provider, nil 260 } 261 262 // we did not find a default provider 263 if res.Status.Code == rpc.Code_CODE_NOT_FOUND { 264 err := errtypes.NotFound(fmt.Sprintf("gateway: default app provider for mime type:%s not found", ri.MimeType)) 265 return nil, err 266 } 267 268 // response code is something else, bubble up error 269 // if a default is not set we abort as returning the first application available is not 270 // deterministic for the end-user as it depends on initialization order of the app approviders with the registry. 271 // It also provides a good hint to the system admin to configure the defaults accordingly. 272 err = errtypes.InternalError(fmt.Sprintf("gateway: unexpected grpc response status:%s when calling GetDefaultAppProviderForMimeType", res.Status)) 273 return nil, err 274 } 275 276 // app has been forced and is set, we try to get an app provider that can satisfy it 277 // Note that we ask for the list of all available providers for a given resource 278 // even though we're only interested into the one set by the "app" parameter. 279 // A better call will be to issue a (to be added) GetAppProviderByName(app) method 280 // to just get what we ask for. 281 res, err := c.GetAppProviders(ctx, ®istry.GetAppProvidersRequest{ 282 ResourceInfo: ri, 283 }) 284 if err != nil { 285 err = errors.Wrap(err, "gateway: error calling GetAppProviders") 286 return nil, err 287 } 288 289 // if the list of app providers is empty means we expect a CODE_NOT_FOUND in the response 290 if res.Status.Code != rpc.Code_CODE_OK { 291 if res.Status.Code == rpc.Code_CODE_NOT_FOUND { 292 return nil, errtypes.NotFound("gateway: app provider not found for resource: " + ri.String()) 293 } 294 return nil, errtypes.InternalError("gateway: error finding app providers") 295 } 296 297 // as long as the above mentioned GetAppProviderByName(app) method is not available 298 // we need to apply a manual filter 299 filteredProviders := []*registry.ProviderInfo{} 300 for _, p := range res.Providers { 301 if p.Name == app { 302 filteredProviders = append(filteredProviders, p) 303 } 304 } 305 res.Providers = filteredProviders 306 307 if len(res.Providers) == 0 { 308 return nil, errtypes.NotFound(fmt.Sprintf("app '%s' not found", app)) 309 } 310 311 if len(res.Providers) == 1 { 312 return res.Providers[0], nil 313 } 314 315 // we should never arrive to the point of having more than one 316 // provider for the given "app" parameters sent by the user 317 return nil, errtypes.InternalError(fmt.Sprintf("gateway: user requested app %q and we provided %d applications", app, len(res.Providers))) 318 319 } 320 321 func getGRPCConfig(opaque *typespb.Opaque) (bool, bool) { 322 if opaque == nil { 323 return false, false 324 } 325 _, insecure := opaque.Map["insecure"] 326 _, skipVerify := opaque.Map["skip-verify"] 327 return insecure, skipVerify 328 } 329 330 func getConn(host string, ins, skipverify bool) (*grpc.ClientConn, error) { 331 if ins { 332 return grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) 333 } 334 335 // TODO(labkode): if in the future we want client-side certificate validation, 336 // we need to load the client cert here 337 tlsconf := &tls.Config{InsecureSkipVerify: skipverify} 338 creds := credentials.NewTLS(tlsconf) 339 return grpc.NewClient(host, grpc.WithTransportCredentials(creds)) 340 }