Initial QSfera import
This commit is contained in:
Generated
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
// 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 appctx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/rs/zerolog"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// name is the Tracer name used to identify this instrumentation library.
|
||||
const tracerName = "appctx"
|
||||
|
||||
// NewUnary returns a new unary interceptor that creates the application context.
|
||||
func NewUnary(log zerolog.Logger, tp trace.TracerProvider) grpc.UnaryServerInterceptor {
|
||||
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
span := trace.SpanFromContext(ctx)
|
||||
defer span.End()
|
||||
if !span.SpanContext().HasTraceID() {
|
||||
ctx, span = tp.Tracer(tracerName).Start(ctx, "grpc unary")
|
||||
}
|
||||
_, file, _, ok := runtime.Caller(1)
|
||||
if ok {
|
||||
span.SetAttributes(semconv.CodeFilePathKey.String(file))
|
||||
}
|
||||
|
||||
sub := log.With().Str("traceid", span.SpanContext().TraceID().String()).Logger()
|
||||
ctx = appctx.WithLogger(ctx, &sub)
|
||||
ctx = appctx.WithTracerProvider(ctx, tp)
|
||||
res, err := handler(ctx, req)
|
||||
return res, err
|
||||
}
|
||||
return interceptor
|
||||
}
|
||||
|
||||
// NewStream returns a new server stream interceptor
|
||||
// that creates the application context.
|
||||
func NewStream(log zerolog.Logger, tp trace.TracerProvider) grpc.StreamServerInterceptor {
|
||||
interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
ctx := ss.Context()
|
||||
span := trace.SpanFromContext(ctx)
|
||||
defer span.End()
|
||||
|
||||
if !span.SpanContext().HasTraceID() {
|
||||
ctx, span = tp.Tracer(tracerName).Start(ctx, "grpc stream")
|
||||
}
|
||||
_, file, _, ok := runtime.Caller(1)
|
||||
if ok {
|
||||
span.SetAttributes(semconv.CodeFilePathKey.String(file))
|
||||
}
|
||||
|
||||
sub := log.With().Str("traceid", span.SpanContext().TraceID().String()).Logger()
|
||||
ctx = appctx.WithLogger(ctx, &sub)
|
||||
|
||||
wrapped := newWrappedServerStream(ctx, ss)
|
||||
err := handler(srv, wrapped)
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
Generated
Vendored
+320
@@ -0,0 +1,320 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bluele/gcache"
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/token"
|
||||
tokenmgr "github.com/opencloud-eu/reva/v2/pkg/token/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// name is the Tracer name used to identify this instrumentation library.
|
||||
const tracerName = "auth"
|
||||
|
||||
var (
|
||||
userGroupsCache gcache.Cache
|
||||
scopeExpansionCache gcache.Cache
|
||||
cacheOnce sync.Once
|
||||
)
|
||||
|
||||
type config struct {
|
||||
// TODO(labkode): access a map is more performant as uri as fixed in length
|
||||
// for SkipMethods.
|
||||
TokenManager string `mapstructure:"token_manager"`
|
||||
TokenManagers map[string]map[string]interface{} `mapstructure:"token_managers"`
|
||||
GatewayAddr string `mapstructure:"gateway_addr"`
|
||||
UserGroupsCacheSize int `mapstructure:"usergroups_cache_size"`
|
||||
ScopeExpansionCacheSize int `mapstructure:"scope_expansion_cache_size"`
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "auth: error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewUnary returns a new unary interceptor that adds
|
||||
// trace information for the request.
|
||||
func NewUnary(m map[string]interface{}, unprotected []string, tp trace.TracerProvider) (grpc.UnaryServerInterceptor, error) {
|
||||
conf, err := parseConfig(m)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "auth: error parsing config")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.TokenManager == "" {
|
||||
conf.TokenManager = "jwt"
|
||||
}
|
||||
conf.GatewayAddr = sharedconf.GetGatewaySVC(conf.GatewayAddr)
|
||||
|
||||
if conf.UserGroupsCacheSize == 0 {
|
||||
conf.UserGroupsCacheSize = 5000
|
||||
}
|
||||
if conf.ScopeExpansionCacheSize == 0 {
|
||||
conf.ScopeExpansionCacheSize = 5000
|
||||
}
|
||||
|
||||
cacheOnce.Do(func() {
|
||||
userGroupsCache = gcache.New(conf.UserGroupsCacheSize).LFU().Build()
|
||||
scopeExpansionCache = gcache.New(conf.ScopeExpansionCacheSize).LFU().Build()
|
||||
})
|
||||
|
||||
h, ok := tokenmgr.NewFuncs[conf.TokenManager]
|
||||
if !ok {
|
||||
return nil, errtypes.NotFound("auth: token manager does not exist: " + conf.TokenManager)
|
||||
}
|
||||
|
||||
tokenManager, err := h(conf.TokenManagers[conf.TokenManager])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "auth: error creating token manager")
|
||||
}
|
||||
|
||||
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
span := trace.SpanFromContext(ctx)
|
||||
defer span.End()
|
||||
if !span.SpanContext().HasTraceID() {
|
||||
ctx, span = tp.Tracer(tracerName).Start(ctx, "grpc auth unary")
|
||||
}
|
||||
|
||||
if utils.Skip(info.FullMethod, unprotected) {
|
||||
log.Debug().Str("method", info.FullMethod).Msg("skipping auth")
|
||||
|
||||
// If a token is present, set it anyway, as we might need the user info
|
||||
// to decide the storage provider.
|
||||
tkn, ok := ctxpkg.ContextGetToken(ctx)
|
||||
if ok {
|
||||
u, tokenScope, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr)
|
||||
if err == nil {
|
||||
// store user and scopes in context
|
||||
ctx = ctxpkg.ContextSetUser(ctx, u)
|
||||
ctx = ctxpkg.ContextSetScopes(ctx, tokenScope)
|
||||
|
||||
span.SetAttributes(semconv.EnduserIDKey.String(u.Id.OpaqueId))
|
||||
}
|
||||
}
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
tkn, ok := ctxpkg.ContextGetToken(ctx)
|
||||
|
||||
if !ok || tkn == "" {
|
||||
log.Warn().Msg("access token not found or empty")
|
||||
return nil, status.Errorf(codes.Unauthenticated, "auth: core access token not found")
|
||||
}
|
||||
|
||||
// validate the token and ensure access to the resource is allowed
|
||||
u, tokenScope, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("access token is invalid")
|
||||
return nil, status.Errorf(codes.PermissionDenied, "auth: core access token is invalid")
|
||||
}
|
||||
if sharedconf.MultiTenantEnabled() && u.GetId().GetType() != userpb.UserType_USER_TYPE_SERVICE && u.GetId().GetTenantId() == "" {
|
||||
log.Warn().Msg("user has no tenant id, rejecting request")
|
||||
return nil, status.Errorf(codes.PermissionDenied, "auth: user has no tenant id, rejecting request")
|
||||
}
|
||||
// store user and scopes in context
|
||||
ctx = ctxpkg.ContextSetUser(ctx, u)
|
||||
ctx = ctxpkg.ContextSetScopes(ctx, tokenScope)
|
||||
|
||||
span.SetAttributes(semconv.EnduserIDKey.String(u.Id.OpaqueId))
|
||||
|
||||
return handler(ctx, req)
|
||||
}
|
||||
return interceptor, nil
|
||||
}
|
||||
|
||||
// NewStream returns a new server stream interceptor
|
||||
// that adds trace information to the request.
|
||||
func NewStream(m map[string]interface{}, unprotected []string, tp trace.TracerProvider) (grpc.StreamServerInterceptor, error) {
|
||||
conf, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.TokenManager == "" {
|
||||
conf.TokenManager = "jwt"
|
||||
}
|
||||
|
||||
if conf.UserGroupsCacheSize == 0 {
|
||||
conf.UserGroupsCacheSize = 10000
|
||||
}
|
||||
if conf.ScopeExpansionCacheSize == 0 {
|
||||
conf.ScopeExpansionCacheSize = 10000
|
||||
}
|
||||
cacheOnce.Do(func() {
|
||||
userGroupsCache = gcache.New(conf.UserGroupsCacheSize).LFU().Build()
|
||||
scopeExpansionCache = gcache.New(conf.ScopeExpansionCacheSize).LFU().Build()
|
||||
})
|
||||
|
||||
h, ok := tokenmgr.NewFuncs[conf.TokenManager]
|
||||
if !ok {
|
||||
return nil, errtypes.NotFound("auth: token manager not found: " + conf.TokenManager)
|
||||
}
|
||||
|
||||
tokenManager, err := h(conf.TokenManagers[conf.TokenManager])
|
||||
if err != nil {
|
||||
return nil, errtypes.NotFound("auth: token manager not found: " + conf.TokenManager)
|
||||
}
|
||||
|
||||
interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
ctx := ss.Context()
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
span := trace.SpanFromContext(ctx)
|
||||
defer span.End()
|
||||
if !span.SpanContext().HasTraceID() {
|
||||
ctx, span = tp.Tracer(tracerName).Start(ctx, "grpc auth new stream")
|
||||
}
|
||||
|
||||
if utils.Skip(info.FullMethod, unprotected) {
|
||||
log.Debug().Str("method", info.FullMethod).Msg("skipping auth")
|
||||
|
||||
// If a token is present, set it anyway, as we might need the user info
|
||||
// to decide the storage provider.
|
||||
tkn, ok := ctxpkg.ContextGetToken(ctx)
|
||||
if ok {
|
||||
u, tokenScope, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr)
|
||||
if err == nil {
|
||||
// store user and scopes in context
|
||||
ctx = ctxpkg.ContextSetUser(ctx, u)
|
||||
ctx = ctxpkg.ContextSetScopes(ctx, tokenScope)
|
||||
ss = newWrappedServerStream(ctx, ss)
|
||||
|
||||
span.SetAttributes(semconv.EnduserIDKey.String(u.Id.OpaqueId))
|
||||
}
|
||||
}
|
||||
|
||||
return handler(srv, ss)
|
||||
}
|
||||
|
||||
tkn, ok := ctxpkg.ContextGetToken(ctx)
|
||||
|
||||
if !ok || tkn == "" {
|
||||
log.Warn().Msg("access token not found")
|
||||
return status.Errorf(codes.Unauthenticated, "auth: core access token not found")
|
||||
}
|
||||
|
||||
// validate the token and ensure access to the resource is allowed
|
||||
u, tokenScope, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("access token is invalid")
|
||||
return status.Errorf(codes.PermissionDenied, "auth: core access token is invalid")
|
||||
}
|
||||
|
||||
// store user and scopes in context
|
||||
ctx = ctxpkg.ContextSetUser(ctx, u)
|
||||
ctx = ctxpkg.ContextSetScopes(ctx, tokenScope)
|
||||
wrapped := newWrappedServerStream(ctx, ss)
|
||||
|
||||
span.SetAttributes(semconv.EnduserIDKey.String(u.Id.OpaqueId))
|
||||
|
||||
return handler(srv, wrapped)
|
||||
}
|
||||
return interceptor, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// dismantleToken extracts the user and scopes from the reva access token
|
||||
func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.Manager, gatewayAddr string) (*userpb.User, map[string]*authpb.Scope, error) {
|
||||
u, tokenScope, err := mgr.DismantleToken(ctx, tkn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if sharedconf.SkipUserGroupsInToken() {
|
||||
client, err := pool.GetGatewayServiceClient(gatewayAddr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
groups, err := getUserGroups(ctx, u, client)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
u.Groups = groups
|
||||
}
|
||||
|
||||
// Check if access to the resource is in the scope of the token
|
||||
ok, err := scope.VerifyScope(ctx, tokenScope, req)
|
||||
if err != nil {
|
||||
return nil, nil, errtypes.InternalError("error verifying scope of access token")
|
||||
}
|
||||
if ok {
|
||||
return u, tokenScope, nil
|
||||
}
|
||||
|
||||
if err = expandAndVerifyScope(ctx, req, tokenScope, u, gatewayAddr, mgr); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return u, tokenScope, nil
|
||||
}
|
||||
|
||||
func getUserGroups(ctx context.Context, u *userpb.User, client gatewayv1beta1.GatewayAPIClient) ([]string, error) {
|
||||
if groupsIf, err := userGroupsCache.Get(u.Id.OpaqueId); err == nil {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Str("userid", u.Id.OpaqueId).Msg("user groups found in cache")
|
||||
return groupsIf.([]string), nil
|
||||
}
|
||||
|
||||
res, err := client.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: u.Id})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetUserGroups")
|
||||
}
|
||||
_ = userGroupsCache.SetWithExpire(u.Id.OpaqueId, res.Groups, 3600*time.Second)
|
||||
|
||||
return res.Groups, nil
|
||||
}
|
||||
Generated
Vendored
+464
@@ -0,0 +1,464 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
statuspkg "github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/token"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
scopeDelimiter = "#"
|
||||
scopeCacheExpiration = 3600
|
||||
)
|
||||
|
||||
func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, user *userpb.User, gatewayAddr string, mgr token.Manager) error {
|
||||
log := appctx.GetLogger(ctx)
|
||||
client, err := pool.GetGatewayServiceClient(gatewayAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ref, ok := extractRef(req, tokenScope); ok {
|
||||
// The request is for a storage reference. This can be the case for multiple scenarios:
|
||||
// - If the path is not empty, the request might be coming from a share where the accessor is
|
||||
// trying to impersonate the owner, since the share manager doesn't know the
|
||||
// share path.
|
||||
// - If the ID not empty, the request might be coming from
|
||||
// - a resource present inside a shared folder, or
|
||||
// - a share created for a lightweight account after the token was minted.
|
||||
log.Info().Msgf("resolving storage reference to check token scope %s", ref.String())
|
||||
for k := range tokenScope {
|
||||
switch {
|
||||
case strings.HasPrefix(k, "publicshare"):
|
||||
if err = resolvePublicShare(ctx, ref, tokenScope[k], client, mgr); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
case strings.HasPrefix(k, "lightweight"):
|
||||
if err = resolveLightweightScope(ctx, ref, tokenScope[k], user, client, mgr); err == nil {
|
||||
return nil
|
||||
}
|
||||
case strings.HasPrefix(k, "ocmshare"):
|
||||
if err = resolveOCMShare(ctx, ref, tokenScope[k], client, mgr); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
log.Err(err).Interface("ref", ref).Interface("scope", k).Msg("error resolving reference under scope")
|
||||
}
|
||||
|
||||
} else if ref, ok := extractShareRef(req); ok {
|
||||
// It's a share ref
|
||||
// The request might be coming from a share created for a lightweight account
|
||||
// after the token was minted.
|
||||
log.Info().Msgf("resolving share reference against received shares to verify token scope %+v", ref.String())
|
||||
for k := range tokenScope {
|
||||
if strings.HasPrefix(k, "lightweight") {
|
||||
// Check if this ID is cached
|
||||
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + ref.GetId().OpaqueId
|
||||
if _, err := scopeExpansionCache.Get(key); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
|
||||
if err != nil || shares.Status.Code != rpc.Code_CODE_OK {
|
||||
log.Warn().Err(err).Msg("error listing received shares")
|
||||
continue
|
||||
}
|
||||
for _, s := range shares.Shares {
|
||||
shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + s.Share.Id.OpaqueId
|
||||
_ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second)
|
||||
|
||||
if ref.GetId() != nil && ref.GetId().OpaqueId == s.Share.Id.OpaqueId {
|
||||
return nil
|
||||
}
|
||||
if key := ref.GetKey(); key != nil && (utils.UserEqual(key.Owner, s.Share.Owner) || utils.UserEqual(key.Owner, s.Share.Creator)) &&
|
||||
utils.ResourceIDEqual(key.ResourceId, s.Share.ResourceId) && utils.GranteeEqual(key.Grantee, s.Share.Grantee) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errtypes.PermissionDenied(fmt.Sprintf("access to resource %+v not allowed within the assigned scope", req))
|
||||
}
|
||||
|
||||
func resolveLightweightScope(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, user *userpb.User, client gateway.GatewayAPIClient, mgr token.Manager) error {
|
||||
refString, err := storagespace.FormatReference(ref)
|
||||
if err != nil {
|
||||
// cannot format reference, so cannot be valid
|
||||
return errtypes.PermissionDenied("invalid reference")
|
||||
}
|
||||
// Check if this ref is cached
|
||||
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + refString
|
||||
if _, err := scopeExpansionCache.Get(key); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
|
||||
if err != nil || shares.Status.Code != rpc.Code_CODE_OK {
|
||||
return errtypes.InternalError("error listing received shares")
|
||||
}
|
||||
|
||||
for _, share := range shares.Shares {
|
||||
shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + storagespace.FormatResourceID(share.Share.ResourceId)
|
||||
_ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second)
|
||||
|
||||
if ref.ResourceId != nil && utils.ResourceIDEqual(share.Share.ResourceId, ref.ResourceId) {
|
||||
return nil
|
||||
}
|
||||
if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok {
|
||||
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errtypes.PermissionDenied("request is not for a nested resource")
|
||||
}
|
||||
|
||||
func resolvePublicShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
|
||||
var share link.PublicShare
|
||||
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
|
||||
}
|
||||
|
||||
func resolveOCMShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
|
||||
var share ocmv1beta1.Share
|
||||
if err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// for ListOCMSharesRequest, the ref resource id is empty and we set path to . to indicate the root of the share
|
||||
if ref.GetResourceId() == nil && ref.Path == "." {
|
||||
ref.ResourceId = share.GetResourceId()
|
||||
}
|
||||
|
||||
return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
|
||||
}
|
||||
|
||||
func checkCacheForNestedResource(ctx context.Context, ref *provider.Reference, resource *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) error {
|
||||
refString, err := storagespace.FormatReference(ref)
|
||||
if err != nil {
|
||||
// cannot format reference, so cannot be valid
|
||||
return errtypes.PermissionDenied("invalid reference")
|
||||
}
|
||||
|
||||
// Check if this ref is cached
|
||||
key := storagespace.FormatResourceID(resource) + scopeDelimiter + refString
|
||||
if _, err := scopeExpansionCache.Get(key); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ok, err := checkIfNestedResource(ctx, ref, resource, client, mgr); err == nil && ok {
|
||||
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errtypes.PermissionDenied("request is not for a nested resource")
|
||||
}
|
||||
|
||||
func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) (bool, error) {
|
||||
// Since the resource ID is obtained from the scope, the current token
|
||||
// has access to it.
|
||||
statResponse, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: parent}})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if statResponse.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor")
|
||||
}
|
||||
|
||||
parentInfo := statResponse.GetInfo()
|
||||
|
||||
// We need to find out if the requested resource is child of the `parent` (coming from token scope)
|
||||
// We mint a token as the owner of the public share and try to stat the reference
|
||||
// TODO(ishank011): We need to find a better alternative to this
|
||||
// NOTE: did somebody say service accounts? ...
|
||||
var user *userpb.User
|
||||
if parentInfo.GetOwner().GetType() == userpb.UserType_USER_TYPE_SPACE_OWNER {
|
||||
// fake a space owner user
|
||||
user = &userpb.User{
|
||||
Id: parentInfo.GetOwner(),
|
||||
}
|
||||
} else {
|
||||
userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: parentInfo.GetOwner(), SkipFetchingUserGroups: true})
|
||||
if err != nil || userResp.Status.Code != rpc.Code_CODE_OK {
|
||||
return false, err
|
||||
}
|
||||
user = userResp.User
|
||||
}
|
||||
scope, err := scope.AddOwnerScope(map[string]*authpb.Scope{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
token, err := mgr.MintToken(ctx, user, scope)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ctx = metadata.AppendToOutgoingContext(context.Background(), ctxpkg.TokenHeader, token)
|
||||
|
||||
childStat, err := client.Stat(ctx, &provider.StatRequest{Ref: ref})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if childStat.GetStatus().GetCode() == rpc.Code_CODE_NOT_FOUND && ref.GetPath() != "" && ref.GetPath() != "." {
|
||||
// The resource does not seem to exist (yet?). We might be part of an initiate upload request.
|
||||
// Stat the parent to get its path and check that against the root path.
|
||||
childStat, err = client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: ref.GetResourceId()}})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if childStat.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return false, statuspkg.NewErrorFromCode(childStat.Status.Code, "auth interceptor")
|
||||
}
|
||||
childInfo := childStat.GetInfo()
|
||||
|
||||
// child can only be a nested resource if it is within the same space as parent
|
||||
if childInfo.GetId().GetStorageId() != parentInfo.GetId().GetStorageId() ||
|
||||
childInfo.GetId().GetSpaceId() != parentInfo.GetId().GetSpaceId() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Both resources are in the same space, now check paths
|
||||
pathResp, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: statResponse.GetInfo().GetId()})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if pathResp.Status.Code != rpc.Code_CODE_OK {
|
||||
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
|
||||
}
|
||||
parentPath := pathResp.Path
|
||||
|
||||
pathResp, err = client.GetPath(ctx, &provider.GetPathRequest{ResourceId: childStat.GetInfo().GetId()})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if pathResp.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
|
||||
}
|
||||
childPath := pathResp.Path
|
||||
rel, err := filepath.Rel(parentPath, childPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !strings.HasPrefix(rel, ".."), nil
|
||||
}
|
||||
|
||||
func extractRefFromListProvidersReq(v *registry.ListStorageProvidersRequest) (*provider.Reference, bool) {
|
||||
ref := &provider.Reference{}
|
||||
if v.Opaque != nil && v.Opaque.Map != nil {
|
||||
if e, ok := v.Opaque.Map["storage_id"]; ok {
|
||||
if ref.ResourceId == nil {
|
||||
ref.ResourceId = &provider.ResourceId{}
|
||||
}
|
||||
ref.ResourceId.StorageId = string(e.Value)
|
||||
}
|
||||
if e, ok := v.Opaque.Map["space_id"]; ok {
|
||||
if ref.ResourceId == nil {
|
||||
ref.ResourceId = &provider.ResourceId{}
|
||||
}
|
||||
ref.ResourceId.SpaceId = string(e.Value)
|
||||
}
|
||||
if e, ok := v.Opaque.Map["opaque_id"]; ok {
|
||||
if ref.ResourceId == nil {
|
||||
ref.ResourceId = &provider.ResourceId{}
|
||||
}
|
||||
ref.ResourceId.OpaqueId = string(e.Value)
|
||||
}
|
||||
if e, ok := v.Opaque.Map["path"]; ok {
|
||||
ref.Path = string(e.Value)
|
||||
}
|
||||
}
|
||||
return ref, true
|
||||
}
|
||||
|
||||
func extractRefForReaderRole(req interface{}) (*provider.Reference, bool) {
|
||||
switch v := req.(type) {
|
||||
// Read requests
|
||||
case *registry.GetStorageProvidersRequest:
|
||||
return v.GetRef(), true
|
||||
case *registry.ListStorageProvidersRequest:
|
||||
return extractRefFromListProvidersReq(v)
|
||||
case *provider.StatRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.ListContainerRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.InitiateFileDownloadRequest:
|
||||
return v.GetRef(), true
|
||||
|
||||
// App provider requests
|
||||
case *appregistry.GetAppProvidersRequest:
|
||||
return &provider.Reference{ResourceId: v.ResourceInfo.Id}, true
|
||||
case *appprovider.OpenInAppRequest:
|
||||
return &provider.Reference{ResourceId: v.ResourceInfo.Id}, true
|
||||
case *gateway.OpenInAppRequest:
|
||||
return v.GetRef(), true
|
||||
|
||||
// Locking
|
||||
case *provider.GetLockRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.SetLockRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.RefreshLockRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.UnlockRequest:
|
||||
return v.GetRef(), true
|
||||
|
||||
// OCM shares
|
||||
case *ocmv1beta1.ListReceivedOCMSharesRequest:
|
||||
return &provider.Reference{Path: "."}, true // we will try to stat the shared node
|
||||
|
||||
}
|
||||
|
||||
return nil, false
|
||||
|
||||
}
|
||||
|
||||
func extractRefForUploaderRole(req interface{}) (*provider.Reference, bool) {
|
||||
switch v := req.(type) {
|
||||
// Write Requests
|
||||
case *registry.GetStorageProvidersRequest:
|
||||
return v.GetRef(), true
|
||||
case *registry.ListStorageProvidersRequest:
|
||||
return extractRefFromListProvidersReq(v)
|
||||
case *provider.StatRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.CreateContainerRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.TouchFileRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.InitiateFileUploadRequest:
|
||||
return v.GetRef(), true
|
||||
|
||||
// App provider requests
|
||||
case *appregistry.GetAppProvidersRequest:
|
||||
return &provider.Reference{ResourceId: v.ResourceInfo.Id}, true
|
||||
case *appprovider.OpenInAppRequest:
|
||||
return &provider.Reference{ResourceId: v.ResourceInfo.Id}, true
|
||||
case *gateway.OpenInAppRequest:
|
||||
return v.GetRef(), true
|
||||
|
||||
// Locking
|
||||
case *provider.GetLockRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.SetLockRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.RefreshLockRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.UnlockRequest:
|
||||
return v.GetRef(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
|
||||
}
|
||||
|
||||
func extractRefForEditorRole(req interface{}) (*provider.Reference, bool) {
|
||||
switch v := req.(type) {
|
||||
// Remaining edit Requests
|
||||
case *provider.DeleteRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.MoveRequest:
|
||||
return v.GetSource(), true
|
||||
case *provider.SetArbitraryMetadataRequest:
|
||||
return v.GetRef(), true
|
||||
case *provider.UnsetArbitraryMetadataRequest:
|
||||
return v.GetRef(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
|
||||
}
|
||||
|
||||
func extractRef(req interface{}, tokenScope map[string]*authpb.Scope) (*provider.Reference, bool) {
|
||||
var readPerm, uploadPerm, editPerm bool
|
||||
for _, v := range tokenScope {
|
||||
if v.Role == authpb.Role_ROLE_OWNER || v.Role == authpb.Role_ROLE_EDITOR || v.Role == authpb.Role_ROLE_VIEWER {
|
||||
readPerm = true
|
||||
}
|
||||
if v.Role == authpb.Role_ROLE_OWNER || v.Role == authpb.Role_ROLE_EDITOR || v.Role == authpb.Role_ROLE_UPLOADER {
|
||||
uploadPerm = true
|
||||
}
|
||||
if v.Role == authpb.Role_ROLE_OWNER || v.Role == authpb.Role_ROLE_EDITOR {
|
||||
editPerm = true
|
||||
}
|
||||
}
|
||||
|
||||
if readPerm {
|
||||
ref, ok := extractRefForReaderRole(req)
|
||||
if ok {
|
||||
return ref, true
|
||||
}
|
||||
}
|
||||
if uploadPerm {
|
||||
ref, ok := extractRefForUploaderRole(req)
|
||||
if ok {
|
||||
return ref, true
|
||||
}
|
||||
}
|
||||
if editPerm {
|
||||
ref, ok := extractRefForEditorRole(req)
|
||||
if ok {
|
||||
return ref, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) {
|
||||
switch v := req.(type) {
|
||||
case *collaboration.GetReceivedShareRequest:
|
||||
return v.GetRef(), true
|
||||
case *collaboration.UpdateReceivedShareRequest:
|
||||
return &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: v.GetShare().GetShare().GetId()}}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
Generated
Vendored
+496
@@ -0,0 +1,496 @@
|
||||
// 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 eventsmiddleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp/router"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
)
|
||||
|
||||
// ContainerCreated converts the response to an event
|
||||
func ContainerCreated(r *provider.CreateContainerResponse, req *provider.CreateContainerRequest, spaceOwner *user.UserId, executant *user.User) events.ContainerCreated {
|
||||
return events.ContainerCreated{
|
||||
SpaceOwner: spaceOwner,
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Ref,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// ShareCreated converts the response to an event
|
||||
func ShareCreated(r *collaboration.CreateShareResponse, executant *user.User) events.ShareCreated {
|
||||
return events.ShareCreated{
|
||||
ShareID: r.Share.GetId(),
|
||||
Executant: executant.GetId(),
|
||||
Sharer: r.Share.Creator,
|
||||
GranteeUserID: r.Share.GetGrantee().GetUserId(),
|
||||
GranteeGroupID: r.Share.GetGrantee().GetGroupId(),
|
||||
ItemID: r.Share.ResourceId,
|
||||
ResourceName: utils.ReadPlainFromOpaque(r.Opaque, "resourcename"),
|
||||
CTime: r.Share.Ctime,
|
||||
Permissions: r.Share.Permissions,
|
||||
}
|
||||
}
|
||||
|
||||
// ShareRemoved converts the response to an event
|
||||
func ShareRemoved(r *collaboration.RemoveShareResponse, req *collaboration.RemoveShareRequest, executant *user.User) events.ShareRemoved {
|
||||
var (
|
||||
userid *user.UserId
|
||||
groupid *group.GroupId
|
||||
rid *provider.ResourceId
|
||||
)
|
||||
_ = utils.ReadJSONFromOpaque(r.Opaque, "granteeuserid", &userid)
|
||||
_ = utils.ReadJSONFromOpaque(r.Opaque, "granteegroupid", &userid)
|
||||
_ = utils.ReadJSONFromOpaque(r.Opaque, "resourceid", &rid)
|
||||
return events.ShareRemoved{
|
||||
Executant: executant.GetId(),
|
||||
ShareID: req.Ref.GetId(),
|
||||
ShareKey: req.Ref.GetKey(),
|
||||
GranteeUserID: userid,
|
||||
GranteeGroupID: groupid,
|
||||
ItemID: rid,
|
||||
ResourceName: utils.ReadPlainFromOpaque(r.Opaque, "resourcename"),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// ShareUpdated converts the response to an event
|
||||
func ShareUpdated(r *collaboration.UpdateShareResponse, req *collaboration.UpdateShareRequest, executant *user.User) events.ShareUpdated {
|
||||
return events.ShareUpdated{
|
||||
Executant: executant.GetId(),
|
||||
ShareID: r.Share.Id,
|
||||
ItemID: r.Share.ResourceId,
|
||||
ResourceName: utils.ReadPlainFromOpaque(r.Opaque, "resourcename"),
|
||||
Permissions: r.Share.Permissions,
|
||||
GranteeUserID: r.Share.GetGrantee().GetUserId(),
|
||||
GranteeGroupID: r.Share.GetGrantee().GetGroupId(),
|
||||
Sharer: r.Share.Creator,
|
||||
MTime: r.Share.Mtime,
|
||||
UpdateMask: req.GetUpdateMask().GetPaths(),
|
||||
}
|
||||
}
|
||||
|
||||
// ReceivedShareUpdated converts the response to an event
|
||||
func ReceivedShareUpdated(r *collaboration.UpdateReceivedShareResponse, executant *user.User) events.ReceivedShareUpdated {
|
||||
return events.ReceivedShareUpdated{
|
||||
Executant: executant.GetId(),
|
||||
ShareID: r.Share.Share.Id,
|
||||
ItemID: r.Share.Share.ResourceId,
|
||||
Permissions: r.Share.Share.Permissions,
|
||||
GranteeUserID: r.Share.Share.GetGrantee().GetUserId(),
|
||||
GranteeGroupID: r.Share.Share.GetGrantee().GetGroupId(),
|
||||
Sharer: r.Share.Share.Creator,
|
||||
MTime: r.Share.Share.Mtime,
|
||||
State: collaboration.ShareState_name[int32(r.Share.State)],
|
||||
}
|
||||
}
|
||||
|
||||
// LinkCreated converts the response to an event
|
||||
func LinkCreated(r *link.CreatePublicShareResponse, executant *user.User) events.LinkCreated {
|
||||
return events.LinkCreated{
|
||||
Executant: executant.GetId(),
|
||||
ShareID: r.Share.Id,
|
||||
Sharer: r.Share.Creator,
|
||||
ItemID: r.Share.ResourceId,
|
||||
ResourceName: utils.ReadPlainFromOpaque(r.Opaque, "resourcename"),
|
||||
Permissions: r.Share.Permissions,
|
||||
DisplayName: r.Share.DisplayName,
|
||||
Expiration: r.Share.Expiration,
|
||||
PasswordProtected: r.Share.PasswordProtected,
|
||||
CTime: r.Share.Ctime,
|
||||
Token: r.Share.Token,
|
||||
}
|
||||
}
|
||||
|
||||
// LinkUpdated converts the response to an event
|
||||
func LinkUpdated(r *link.UpdatePublicShareResponse, req *link.UpdatePublicShareRequest, executant *user.User) events.LinkUpdated {
|
||||
return events.LinkUpdated{
|
||||
Executant: executant.GetId(),
|
||||
ShareID: r.Share.Id,
|
||||
Sharer: r.Share.Creator,
|
||||
ItemID: r.Share.ResourceId,
|
||||
ResourceName: utils.ReadPlainFromOpaque(r.Opaque, "resourcename"),
|
||||
Permissions: r.Share.Permissions,
|
||||
DisplayName: r.Share.DisplayName,
|
||||
Expiration: r.Share.Expiration,
|
||||
PasswordProtected: r.Share.PasswordProtected,
|
||||
MTime: r.Share.Mtime,
|
||||
Token: r.Share.Token,
|
||||
FieldUpdated: link.UpdatePublicShareRequest_Update_Type_name[int32(req.Update.GetType())],
|
||||
}
|
||||
}
|
||||
|
||||
// LinkAccessed converts the response to an event
|
||||
func LinkAccessed(r *link.GetPublicShareByTokenResponse, executant *user.User) events.LinkAccessed {
|
||||
return events.LinkAccessed{
|
||||
Executant: executant.GetId(),
|
||||
ShareID: r.Share.Id,
|
||||
Sharer: r.Share.Creator,
|
||||
ItemID: r.Share.ResourceId,
|
||||
Permissions: r.Share.Permissions,
|
||||
DisplayName: r.Share.DisplayName,
|
||||
Expiration: r.Share.Expiration,
|
||||
PasswordProtected: r.Share.PasswordProtected,
|
||||
CTime: r.Share.Ctime,
|
||||
Token: r.Share.Token,
|
||||
}
|
||||
}
|
||||
|
||||
// LinkAccessFailed converts the response to an event
|
||||
func LinkAccessFailed(r *link.GetPublicShareByTokenResponse, req *link.GetPublicShareByTokenRequest, executant *user.User) events.LinkAccessFailed {
|
||||
e := events.LinkAccessFailed{
|
||||
Executant: executant.GetId(),
|
||||
Status: r.Status.Code,
|
||||
Message: r.Status.Message,
|
||||
Timestamp: utils.TSNow(),
|
||||
Token: req.Token,
|
||||
}
|
||||
if r.Share != nil {
|
||||
e.ShareID = r.Share.Id
|
||||
e.Token = r.Share.Token
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// LinkRemoved converts the response to an event
|
||||
func LinkRemoved(r *link.RemovePublicShareResponse, req *link.RemovePublicShareRequest, executant *user.User) events.LinkRemoved {
|
||||
var rid *provider.ResourceId
|
||||
_ = utils.ReadJSONFromOpaque(r.Opaque, "resourceid", &rid)
|
||||
return events.LinkRemoved{
|
||||
Executant: executant.GetId(),
|
||||
ShareID: req.Ref.GetId(),
|
||||
ShareToken: req.Ref.GetToken(),
|
||||
Timestamp: utils.TSNow(),
|
||||
ItemID: rid,
|
||||
ResourceName: utils.ReadPlainFromOpaque(r.Opaque, "resourcename"),
|
||||
}
|
||||
}
|
||||
|
||||
func OCMCoreShareCreated(r *ocmcore.CreateOCMCoreShareResponse, req *ocmcore.CreateOCMCoreShareRequest, executant *user.User) events.OCMCoreShareCreated {
|
||||
var permissions *provider.ResourcePermissions
|
||||
for _, p := range req.GetProtocols() {
|
||||
if p.GetWebdavOptions() != nil {
|
||||
permissions = p.GetWebdavOptions().GetPermissions().GetPermissions()
|
||||
break
|
||||
}
|
||||
}
|
||||
return events.OCMCoreShareCreated{
|
||||
ShareID: r.GetId(),
|
||||
Executant: executant.GetId(),
|
||||
Sharer: req.GetSender(),
|
||||
GranteeUserID: req.GetShareWith(),
|
||||
ItemID: req.GetResourceId(),
|
||||
ResourceName: req.GetName(),
|
||||
CTime: r.GetCreated(),
|
||||
Permissions: permissions,
|
||||
}
|
||||
}
|
||||
|
||||
// FileTouched converts the response to an event
|
||||
func FileTouched(r *provider.TouchFileResponse, req *provider.TouchFileRequest, spaceOwner *user.UserId, executant *user.User) events.FileTouched {
|
||||
return events.FileTouched{
|
||||
SpaceOwner: spaceOwner,
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Ref,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// FileUploaded converts the response to an event
|
||||
func FileUploaded(r *provider.InitiateFileUploadResponse, req *provider.InitiateFileUploadRequest, spaceOwner *user.UserId, executant *user.User) events.FileUploaded {
|
||||
return events.FileUploaded{
|
||||
SpaceOwner: spaceOwner,
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Ref,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// FileDownloaded converts the response to an event
|
||||
func FileDownloaded(r *provider.InitiateFileDownloadResponse, req *provider.InitiateFileDownloadRequest, executant *user.User) events.FileDownloaded {
|
||||
return events.FileDownloaded{
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Ref,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// FileLocked converts the response to an events
|
||||
func FileLocked(r *provider.SetLockResponse, req *provider.SetLockRequest, owner *user.UserId, executant *user.User) events.FileLocked {
|
||||
return events.FileLocked{
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Ref,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// FileUnlocked converts the response to an event
|
||||
func FileUnlocked(r *provider.UnlockResponse, req *provider.UnlockRequest, owner *user.UserId, executant *user.User) events.FileUnlocked {
|
||||
return events.FileUnlocked{
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Ref,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// ItemTrashed converts the response to an event
|
||||
func ItemTrashed(r *provider.DeleteResponse, req *provider.DeleteRequest, spaceOwner *user.UserId, executant *user.User) events.ItemTrashed {
|
||||
opaqueID := utils.ReadPlainFromOpaque(r.Opaque, "opaque_id")
|
||||
return events.ItemTrashed{
|
||||
SpaceOwner: spaceOwner,
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Ref,
|
||||
ID: &provider.ResourceId{
|
||||
StorageId: req.Ref.GetResourceId().GetStorageId(),
|
||||
SpaceId: req.Ref.GetResourceId().GetSpaceId(),
|
||||
OpaqueId: opaqueID,
|
||||
},
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// ItemMoved converts the response to an event
|
||||
func ItemMoved(r *provider.MoveResponse, req *provider.MoveRequest, spaceOwner *user.UserId, executant *user.User) events.ItemMoved {
|
||||
return events.ItemMoved{
|
||||
SpaceOwner: spaceOwner,
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Destination,
|
||||
OldReference: req.Source,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// TrashbinPurged converts the response to an event
|
||||
func TrashbinPurged(r *provider.PurgeRecycleResponse, req *provider.PurgeRecycleRequest, executant *user.User) events.TrashbinPurged {
|
||||
return events.TrashbinPurged{
|
||||
Executant: executant.GetId(),
|
||||
Ref: &provider.Reference{
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: req.Ref.GetResourceId().GetStorageId(),
|
||||
SpaceId: req.Ref.GetResourceId().GetSpaceId(),
|
||||
OpaqueId: req.Ref.GetResourceId().GetSpaceId(),
|
||||
},
|
||||
},
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// ItemPurged converts the response to an event
|
||||
func ItemPurged(r *provider.PurgeRecycleResponse, req *provider.PurgeRecycleRequest, executant *user.User) events.ItemPurged {
|
||||
root, relativePath := router.ShiftPath(req.Key)
|
||||
if relativePath == "/" && !strings.HasSuffix(req.Key, "/") {
|
||||
relativePath = ""
|
||||
}
|
||||
if root == "" {
|
||||
// if there is no key this is about purging the whole trashbin
|
||||
root = req.Ref.GetResourceId().GetSpaceId()
|
||||
}
|
||||
|
||||
ref := &provider.Reference{
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: req.Ref.GetResourceId().GetStorageId(),
|
||||
SpaceId: req.Ref.GetResourceId().GetSpaceId(),
|
||||
OpaqueId: root,
|
||||
},
|
||||
Path: relativePath,
|
||||
}
|
||||
|
||||
return events.ItemPurged{
|
||||
Executant: executant.GetId(),
|
||||
Ref: ref,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// ItemRestored converts the response to an event
|
||||
func ItemRestored(r *provider.RestoreRecycleItemResponse, req *provider.RestoreRecycleItemRequest, spaceOwner *user.UserId, executant *user.User) events.ItemRestored {
|
||||
ref := req.Ref
|
||||
if req.RestoreRef != nil {
|
||||
ref = req.RestoreRef
|
||||
}
|
||||
return events.ItemRestored{
|
||||
SpaceOwner: spaceOwner,
|
||||
Executant: executant.GetId(),
|
||||
Ref: ref,
|
||||
OldReference: req.Ref,
|
||||
Key: req.Key,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// FileVersionRestored converts the response to an event
|
||||
func FileVersionRestored(r *provider.RestoreFileVersionResponse, req *provider.RestoreFileVersionRequest, spaceOwner *user.UserId, executant *user.User) events.FileVersionRestored {
|
||||
return events.FileVersionRestored{
|
||||
SpaceOwner: spaceOwner,
|
||||
Executant: executant.GetId(),
|
||||
Ref: req.Ref,
|
||||
Key: req.Key,
|
||||
Timestamp: utils.TSNow(),
|
||||
ImpersonatingUser: extractImpersonator(executant),
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceCreated converts the response to an event
|
||||
func SpaceCreated(r *provider.CreateStorageSpaceResponse, executant *user.User) events.SpaceCreated {
|
||||
return events.SpaceCreated{
|
||||
Executant: executant.GetId(),
|
||||
ID: r.StorageSpace.Id,
|
||||
Owner: extractOwner(r.StorageSpace.Owner),
|
||||
Root: r.StorageSpace.Root,
|
||||
Name: r.StorageSpace.Name,
|
||||
Type: r.StorageSpace.SpaceType,
|
||||
Quota: r.StorageSpace.Quota,
|
||||
MTime: r.StorageSpace.Mtime,
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceRenamed converts the response to an event
|
||||
func SpaceRenamed(r *provider.UpdateStorageSpaceResponse, req *provider.UpdateStorageSpaceRequest, executant *user.User) events.SpaceRenamed {
|
||||
return events.SpaceRenamed{
|
||||
Executant: executant.GetId(),
|
||||
ID: r.StorageSpace.Id,
|
||||
Owner: extractOwner(r.StorageSpace.Owner),
|
||||
Name: r.StorageSpace.Name,
|
||||
Timestamp: utils.TSNow(),
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceUpdated converts the response to an event
|
||||
func SpaceUpdated(r *provider.UpdateStorageSpaceResponse, req *provider.UpdateStorageSpaceRequest, executant *user.User) events.SpaceUpdated {
|
||||
return events.SpaceUpdated{
|
||||
Executant: executant.GetId(),
|
||||
ID: r.StorageSpace.Id,
|
||||
Space: r.StorageSpace,
|
||||
Timestamp: utils.TSNow(),
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceEnabled converts the response to an event
|
||||
func SpaceEnabled(r *provider.UpdateStorageSpaceResponse, req *provider.UpdateStorageSpaceRequest, executant *user.User) events.SpaceEnabled {
|
||||
return events.SpaceEnabled{
|
||||
Executant: executant.GetId(),
|
||||
ID: r.StorageSpace.Id,
|
||||
Owner: extractOwner(r.StorageSpace.Owner),
|
||||
Timestamp: utils.TSNow(),
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceShared converts the response to an event
|
||||
func SpaceShared(r *collaboration.CreateShareResponse, executant *user.User) events.SpaceShared {
|
||||
id := storagespace.FormatStorageID(r.GetShare().GetResourceId().GetStorageId(), r.GetShare().GetResourceId().GetSpaceId())
|
||||
return events.SpaceShared{
|
||||
Executant: executant.GetId(),
|
||||
Creator: r.Share.GetCreator(),
|
||||
GranteeUserID: r.Share.GetGrantee().GetUserId(),
|
||||
GranteeGroupID: r.Share.GetGrantee().GetGroupId(),
|
||||
ID: &provider.StorageSpaceId{OpaqueId: id},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceShareUpdated converts the response to an events
|
||||
func SpaceShareUpdated(r *collaboration.UpdateShareResponse, executant *user.User) events.SpaceShareUpdated {
|
||||
id := storagespace.FormatStorageID(r.GetShare().GetResourceId().GetStorageId(), r.GetShare().GetResourceId().GetSpaceId())
|
||||
return events.SpaceShareUpdated{
|
||||
Executant: executant.GetId(),
|
||||
GranteeUserID: r.GetShare().GetGrantee().GetUserId(),
|
||||
GranteeGroupID: r.GetShare().GetGrantee().GetGroupId(),
|
||||
ID: &provider.StorageSpaceId{OpaqueId: id},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceUnshared converts the response to an event
|
||||
func SpaceUnshared(r *collaboration.RemoveShareResponse, req *collaboration.RemoveShareRequest, executant *user.User) events.SpaceUnshared {
|
||||
var (
|
||||
userid *user.UserId
|
||||
groupid *group.GroupId
|
||||
rid *provider.ResourceId
|
||||
)
|
||||
_ = utils.ReadJSONFromOpaque(r.Opaque, "granteeuserid", &userid)
|
||||
_ = utils.ReadJSONFromOpaque(r.Opaque, "granteegroupid", &groupid)
|
||||
_ = utils.ReadJSONFromOpaque(r.Opaque, "resourceid", &rid)
|
||||
id := storagespace.FormatStorageID(rid.StorageId, rid.SpaceId)
|
||||
return events.SpaceUnshared{
|
||||
Executant: executant.GetId(),
|
||||
GranteeUserID: userid,
|
||||
GranteeGroupID: groupid,
|
||||
ID: &provider.StorageSpaceId{OpaqueId: id},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceDisabled converts the response to an event
|
||||
func SpaceDisabled(r *provider.DeleteStorageSpaceResponse, req *provider.DeleteStorageSpaceRequest, executant *user.User) events.SpaceDisabled {
|
||||
return events.SpaceDisabled{
|
||||
Executant: executant.GetId(),
|
||||
ID: req.Id,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceDeleted converts the response to an event
|
||||
func SpaceDeleted(r *provider.DeleteStorageSpaceResponse, req *provider.DeleteStorageSpaceRequest, executant *user.User) events.SpaceDeleted {
|
||||
var final map[string]provider.ResourcePermissions
|
||||
_ = utils.ReadJSONFromOpaque(r.GetOpaque(), "grants", &final)
|
||||
return events.SpaceDeleted{
|
||||
Executant: executant.GetId(),
|
||||
ID: req.Id,
|
||||
SpaceName: utils.ReadPlainFromOpaque(r.GetOpaque(), "spacename"),
|
||||
FinalMembers: final,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func extractOwner(u *user.User) *user.UserId {
|
||||
if u != nil {
|
||||
return u.Id
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractImpersonator(u *user.User) *user.User {
|
||||
var impersonator user.User
|
||||
if err := utils.ReadJSONFromOpaque(u.Opaque, "impersonating-user", &impersonator); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &impersonator
|
||||
}
|
||||
Generated
Vendored
+246
@@ -0,0 +1,246 @@
|
||||
// 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 eventsmiddleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPriority = 200
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.RegisterUnaryInterceptor("eventsmiddleware", NewUnary)
|
||||
}
|
||||
|
||||
// NewUnary returns a new unary interceptor that emits events when needed
|
||||
// no lint because of the switch statement that should be extendable
|
||||
//
|
||||
//nolint:gocritic
|
||||
func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error) {
|
||||
publisher, err := publisherFromConfig(m)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
// Register a channel in the context to receive the space owner id from the handler(s) further down the stack
|
||||
var ownerID *user.UserId
|
||||
sendOwnerChan := make(chan *user.UserId)
|
||||
ctx = storagespace.ContextRegisterSendOwnerChan(ctx, sendOwnerChan)
|
||||
|
||||
res, err := handler(ctx, req)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Read the space owner id from the channel
|
||||
select {
|
||||
case ownerID = <-sendOwnerChan:
|
||||
default:
|
||||
}
|
||||
|
||||
executant, _ := revactx.ContextGetUser(ctx)
|
||||
|
||||
// The MoveResponse event is moved to the decomposedfs
|
||||
var ev interface{}
|
||||
switch v := res.(type) {
|
||||
case *collaboration.CreateShareResponse:
|
||||
if isSuccess(v) {
|
||||
if utils.ExistsInOpaque(v.Opaque, "noevent") {
|
||||
break
|
||||
}
|
||||
if utils.ExistsInOpaque(v.Opaque, "spacegrant") {
|
||||
ev = SpaceShared(v, executant)
|
||||
} else {
|
||||
ev = ShareCreated(v, executant)
|
||||
}
|
||||
}
|
||||
case *collaboration.RemoveShareResponse:
|
||||
if isSuccess(v) {
|
||||
if utils.ExistsInOpaque(v.Opaque, "spacegrant") {
|
||||
ev = SpaceUnshared(v, req.(*collaboration.RemoveShareRequest), executant)
|
||||
} else {
|
||||
ev = ShareRemoved(v, req.(*collaboration.RemoveShareRequest), executant)
|
||||
}
|
||||
}
|
||||
case *collaboration.UpdateShareResponse:
|
||||
if isSuccess(v) {
|
||||
if utils.ExistsInOpaque(v.Opaque, "spacegrant") {
|
||||
ev = SpaceShareUpdated(v, executant)
|
||||
} else {
|
||||
ev = ShareUpdated(v, req.(*collaboration.UpdateShareRequest), executant)
|
||||
}
|
||||
}
|
||||
case *collaboration.UpdateReceivedShareResponse:
|
||||
if isSuccess(v) {
|
||||
ev = ReceivedShareUpdated(v, executant)
|
||||
}
|
||||
case *link.CreatePublicShareResponse:
|
||||
if isSuccess(v) {
|
||||
ev = LinkCreated(v, executant)
|
||||
}
|
||||
case *link.UpdatePublicShareResponse:
|
||||
if isSuccess(v) {
|
||||
ev = LinkUpdated(v, req.(*link.UpdatePublicShareRequest), executant)
|
||||
}
|
||||
case *link.RemovePublicShareResponse:
|
||||
if isSuccess(v) {
|
||||
ev = LinkRemoved(v, req.(*link.RemovePublicShareRequest), executant)
|
||||
}
|
||||
case *link.GetPublicShareByTokenResponse:
|
||||
if isSuccess(v) {
|
||||
ev = LinkAccessed(v, executant)
|
||||
} else {
|
||||
ev = LinkAccessFailed(v, req.(*link.GetPublicShareByTokenRequest), executant)
|
||||
}
|
||||
case *ocmcore.CreateOCMCoreShareResponse:
|
||||
if isSuccess(v) {
|
||||
ev = OCMCoreShareCreated(v, req.(*ocmcore.CreateOCMCoreShareRequest), executant)
|
||||
}
|
||||
case *provider.CreateContainerResponse:
|
||||
if isSuccess(v) {
|
||||
ev = ContainerCreated(v, req.(*provider.CreateContainerRequest), ownerID, executant)
|
||||
}
|
||||
case *provider.InitiateFileDownloadResponse:
|
||||
if isSuccess(v) {
|
||||
ev = FileDownloaded(v, req.(*provider.InitiateFileDownloadRequest), executant)
|
||||
}
|
||||
case *provider.DeleteResponse:
|
||||
if isSuccess(v) {
|
||||
ev = ItemTrashed(v, req.(*provider.DeleteRequest), ownerID, executant)
|
||||
}
|
||||
case *provider.PurgeRecycleResponse:
|
||||
if isSuccess(v) {
|
||||
id := req.(*provider.PurgeRecycleRequest).GetRef().GetResourceId()
|
||||
if id != nil && id.OpaqueId == id.SpaceId && req.(*provider.PurgeRecycleRequest).Key == "" {
|
||||
// this is a purge of the whole trashbin, NOT the whole space
|
||||
ev = TrashbinPurged(v, req.(*provider.PurgeRecycleRequest), executant)
|
||||
} else {
|
||||
ev = ItemPurged(v, req.(*provider.PurgeRecycleRequest), executant)
|
||||
}
|
||||
}
|
||||
case *provider.RestoreRecycleItemResponse:
|
||||
if isSuccess(v) {
|
||||
ev = ItemRestored(v, req.(*provider.RestoreRecycleItemRequest), ownerID, executant)
|
||||
}
|
||||
case *provider.RestoreFileVersionResponse:
|
||||
if isSuccess(v) {
|
||||
ev = FileVersionRestored(v, req.(*provider.RestoreFileVersionRequest), ownerID, executant)
|
||||
}
|
||||
case *provider.CreateStorageSpaceResponse:
|
||||
if isSuccess(v) && v.StorageSpace != nil { // TODO: Why are there CreateStorageSpaceResponses with nil StorageSpace?
|
||||
ev = SpaceCreated(v, executant)
|
||||
}
|
||||
case *provider.UpdateStorageSpaceResponse:
|
||||
if isSuccess(v) {
|
||||
r := req.(*provider.UpdateStorageSpaceRequest)
|
||||
if r.StorageSpace.Name != "" {
|
||||
ev = SpaceRenamed(v, r, executant)
|
||||
} else if utils.ExistsInOpaque(r.Opaque, "restore") {
|
||||
ev = SpaceEnabled(v, r, executant)
|
||||
} else {
|
||||
ev = SpaceUpdated(v, r, executant)
|
||||
}
|
||||
}
|
||||
case *provider.DeleteStorageSpaceResponse:
|
||||
if isSuccess(v) {
|
||||
r := req.(*provider.DeleteStorageSpaceRequest)
|
||||
if utils.ExistsInOpaque(r.Opaque, "purge") {
|
||||
ev = SpaceDeleted(v, r, executant)
|
||||
} else {
|
||||
ev = SpaceDisabled(v, r, executant)
|
||||
}
|
||||
}
|
||||
case *provider.TouchFileResponse:
|
||||
if isSuccess(v) {
|
||||
ev = FileTouched(v, req.(*provider.TouchFileRequest), ownerID, executant)
|
||||
}
|
||||
case *provider.SetLockResponse:
|
||||
if isSuccess(v) {
|
||||
ev = FileLocked(v, req.(*provider.SetLockRequest), ownerID, executant)
|
||||
}
|
||||
case *provider.UnlockResponse:
|
||||
if isSuccess(v) {
|
||||
ev = FileUnlocked(v, req.(*provider.UnlockRequest), ownerID, executant)
|
||||
}
|
||||
}
|
||||
|
||||
if ev != nil {
|
||||
if err := events.Publish(ctx, publisher, ev); err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Interface("event", ev).Msg("publishing event failed")
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
return interceptor, defaultPriority, nil
|
||||
}
|
||||
|
||||
// NewStream returns a new server stream interceptor
|
||||
// that creates the application context.
|
||||
func NewStream() grpc.StreamServerInterceptor {
|
||||
interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
// TODO: Use ss.RecvMsg() and ss.SendMsg() to send events from a stream
|
||||
return handler(srv, ss)
|
||||
}
|
||||
return interceptor
|
||||
}
|
||||
|
||||
// common interface to all responses
|
||||
type su interface {
|
||||
GetStatus() *rpc.Status
|
||||
}
|
||||
|
||||
func isSuccess(res su) bool {
|
||||
return res.GetStatus().Code == rpc.Code_CODE_OK
|
||||
}
|
||||
|
||||
func publisherFromConfig(m map[string]interface{}) (events.Publisher, error) {
|
||||
typ := m["type"].(string)
|
||||
switch typ {
|
||||
default:
|
||||
return nil, fmt.Errorf("stream type '%s' not supported", typ)
|
||||
case "nats":
|
||||
var cfg stream.NatsConfig
|
||||
if err := mapstructure.Decode(m, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name, _ := m["name"].(string)
|
||||
return stream.NatsFromConfig(name, false, cfg)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
// 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 loader
|
||||
|
||||
import (
|
||||
// Load core GRPC services
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/interceptors/eventsmiddleware"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/interceptors/prometheus"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/interceptors/readonly"
|
||||
// Add your own service here
|
||||
)
|
||||
Generated
Vendored
+117
@@ -0,0 +1,117 @@
|
||||
// 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 log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// NewUnary returns a new unary interceptor
|
||||
// that logs grpc calls.
|
||||
func NewUnary() grpc.UnaryServerInterceptor {
|
||||
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
start := time.Now()
|
||||
res, err := handler(ctx, req)
|
||||
code := status.Code(err)
|
||||
end := time.Now()
|
||||
diff := end.Sub(start).Nanoseconds()
|
||||
var fromAddress string
|
||||
if p, ok := peer.FromContext(ctx); ok {
|
||||
fromAddress = p.Addr.Network() + "://" + p.Addr.String()
|
||||
}
|
||||
userAgent, ok := ctxpkg.ContextGetUserAgentString(ctx)
|
||||
if !ok {
|
||||
userAgent = ""
|
||||
}
|
||||
|
||||
log := appctx.GetLogger(ctx)
|
||||
var event *zerolog.Event
|
||||
var msg string
|
||||
if code != codes.OK {
|
||||
event = log.Error()
|
||||
msg = err.Error()
|
||||
} else {
|
||||
event = log.Debug()
|
||||
msg = "unary"
|
||||
}
|
||||
|
||||
event.Str("user-agent", userAgent).
|
||||
Str("from", fromAddress).
|
||||
Str("uri", info.FullMethod).
|
||||
Str("start", start.Format("02/Jan/2006:15:04:05 -0700")).
|
||||
Str("end", end.Format("02/Jan/2006:15:04:05 -0700")).Int("time_ns", int(diff)).
|
||||
Str("code", code.String()).
|
||||
Msg(msg)
|
||||
|
||||
return res, err
|
||||
}
|
||||
return interceptor
|
||||
}
|
||||
|
||||
// NewStream returns a new server stream interceptor
|
||||
// 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 string
|
||||
if p, ok := peer.FromContext(ss.Context()); ok {
|
||||
fromAddress = p.Addr.Network() + "://" + p.Addr.String()
|
||||
}
|
||||
userAgent, ok := ctxpkg.ContextGetUserAgentString(ctx)
|
||||
if !ok {
|
||||
userAgent = ""
|
||||
}
|
||||
|
||||
log := appctx.GetLogger(ss.Context())
|
||||
var event *zerolog.Event
|
||||
var msg string
|
||||
if code != codes.OK {
|
||||
event = log.Error()
|
||||
msg = err.Error()
|
||||
} else {
|
||||
event = log.Debug()
|
||||
msg = "stream"
|
||||
}
|
||||
|
||||
event.Str("user-agent", userAgent).
|
||||
Str("from", fromAddress).
|
||||
Str("uri", info.FullMethod).
|
||||
Str("start", start.Format("02/Jan/2006:15:04:05 -0700")).
|
||||
Str("end", end.Format("02/Jan/2006:15:04:05 -0700")).Int("time_ns", int(diff)).
|
||||
Str("code", code.String()).
|
||||
Msg(msg)
|
||||
|
||||
return err
|
||||
}
|
||||
return interceptor
|
||||
}
|
||||
Generated
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
// 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 prometheus
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPriority = 100
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.RegisterUnaryInterceptor("prometheus", NewUnary)
|
||||
}
|
||||
|
||||
// NewUnary returns a new unary interceptor
|
||||
// that counts grpc calls.
|
||||
func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error) {
|
||||
interceptor, err := interceptorFromConfig(m)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return interceptor, defaultPriority, nil
|
||||
}
|
||||
|
||||
// NewStream returns a new server stream interceptor
|
||||
// that counts grpc calls.
|
||||
func NewStream() grpc.StreamServerInterceptor {
|
||||
interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
err := handler(srv, ss)
|
||||
// TODO count res codes & errors
|
||||
return err
|
||||
}
|
||||
return interceptor
|
||||
}
|
||||
|
||||
func interceptorFromConfig(m map[string]interface{}) (grpc.UnaryServerInterceptor, error) {
|
||||
namespace := m["namespace"].(string)
|
||||
if namespace == "" {
|
||||
namespace = "reva"
|
||||
}
|
||||
subsystem := m["subsystem"].(string)
|
||||
reqProcessed := promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "grpc_requests_total",
|
||||
Help: "The total number of processed " + subsystem + " GRPC requests for " + namespace,
|
||||
})
|
||||
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
res, err := handler(ctx, req)
|
||||
reqProcessed.Inc()
|
||||
return res, err
|
||||
}
|
||||
return interceptor, nil
|
||||
}
|
||||
Generated
Vendored
+150
@@ -0,0 +1,150 @@
|
||||
// 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 readonly
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
rstatus "github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPriority = 200
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.RegisterUnaryInterceptor("readonly", NewUnary)
|
||||
}
|
||||
|
||||
// NewUnary returns a new unary interceptor
|
||||
// that checks grpc calls and blocks write requests.
|
||||
func NewUnary(map[string]interface{}) (grpc.UnaryServerInterceptor, int, error) {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
switch req.(type) {
|
||||
// handle known non-write request types
|
||||
case *provider.GetHomeRequest,
|
||||
*provider.GetPathRequest,
|
||||
*provider.GetQuotaRequest,
|
||||
*registry.GetStorageProvidersRequest,
|
||||
*provider.InitiateFileDownloadRequest,
|
||||
*provider.ListFileVersionsRequest,
|
||||
*provider.ListGrantsRequest,
|
||||
*provider.ListRecycleRequest:
|
||||
return handler(ctx, req)
|
||||
case *provider.ListContainerRequest:
|
||||
resp, err := handler(ctx, req)
|
||||
if listResp, ok := resp.(*provider.ListContainerResponse); ok && listResp.Infos != nil {
|
||||
for _, info := range listResp.Infos {
|
||||
// use the existing PermissionsSet and change the writes to false
|
||||
if info.PermissionSet != nil {
|
||||
info.PermissionSet.AddGrant = false
|
||||
info.PermissionSet.CreateContainer = false
|
||||
info.PermissionSet.Delete = false
|
||||
info.PermissionSet.InitiateFileUpload = false
|
||||
info.PermissionSet.Move = false
|
||||
info.PermissionSet.RemoveGrant = false
|
||||
info.PermissionSet.PurgeRecycle = false
|
||||
info.PermissionSet.RestoreFileVersion = false
|
||||
info.PermissionSet.RestoreRecycleItem = false
|
||||
info.PermissionSet.UpdateGrant = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
case *provider.StatRequest:
|
||||
resp, err := handler(ctx, req)
|
||||
if statResp, ok := resp.(*provider.StatResponse); ok && statResp.Info != nil && statResp.Info.PermissionSet != nil {
|
||||
// use the existing PermissionsSet and change the writes to false
|
||||
statResp.Info.PermissionSet.AddGrant = false
|
||||
statResp.Info.PermissionSet.CreateContainer = false
|
||||
statResp.Info.PermissionSet.Delete = false
|
||||
statResp.Info.PermissionSet.InitiateFileUpload = false
|
||||
statResp.Info.PermissionSet.Move = false
|
||||
statResp.Info.PermissionSet.RemoveGrant = false
|
||||
statResp.Info.PermissionSet.PurgeRecycle = false
|
||||
statResp.Info.PermissionSet.RestoreFileVersion = false
|
||||
statResp.Info.PermissionSet.RestoreRecycleItem = false
|
||||
statResp.Info.PermissionSet.UpdateGrant = false
|
||||
}
|
||||
return resp, err
|
||||
// Don't allow the following requests types
|
||||
case *provider.AddGrantRequest:
|
||||
return &provider.AddGrantResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to add grant on readonly storage"),
|
||||
}, nil
|
||||
case *provider.CreateContainerRequest:
|
||||
return &provider.CreateContainerResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to create resource on read-only storage"),
|
||||
}, nil
|
||||
case *provider.TouchFileRequest:
|
||||
return &provider.TouchFileResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to create resource on read-only storage"),
|
||||
}, nil
|
||||
case *provider.CreateHomeRequest:
|
||||
return &provider.CreateHomeResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to create home on readonly storage"),
|
||||
}, nil
|
||||
case *provider.DeleteRequest:
|
||||
return &provider.DeleteResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to delete resource on readonly storage"),
|
||||
}, nil
|
||||
case *provider.InitiateFileUploadRequest:
|
||||
return &provider.InitiateFileUploadResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to upload resource on readonly storage"),
|
||||
}, nil
|
||||
case *provider.MoveRequest:
|
||||
return &provider.MoveResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to move resource on readonly storage"),
|
||||
}, nil
|
||||
case *provider.PurgeRecycleRequest:
|
||||
return &provider.PurgeRecycleResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to purge recycle on readonly storage"),
|
||||
}, nil
|
||||
case *provider.RemoveGrantRequest:
|
||||
return &provider.RemoveGrantResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to remove grant on readonly storage"),
|
||||
}, nil
|
||||
case *provider.RestoreRecycleItemRequest:
|
||||
return &provider.RestoreRecycleItemResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to restore recycle item on readonly storage"),
|
||||
}, nil
|
||||
case *provider.SetArbitraryMetadataRequest:
|
||||
return &provider.SetArbitraryMetadataResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to set arbitrary metadata on readonly storage"),
|
||||
}, nil
|
||||
case *provider.UnsetArbitraryMetadataRequest:
|
||||
return &provider.UnsetArbitraryMetadataResponse{
|
||||
Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to unset arbitrary metadata on readonly storage"),
|
||||
}, nil
|
||||
// block unknown request types and return error
|
||||
default:
|
||||
log.Debug().Msg("storage is readonly")
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied: tried to execute an unknown operation: %T!", req)
|
||||
}
|
||||
}, defaultPriority, nil
|
||||
}
|
||||
Generated
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
// 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 recovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"runtime/debug"
|
||||
|
||||
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// NewUnary returns a server interceptor that adds telemetry to
|
||||
// grpc calls.
|
||||
func NewUnary() grpc.UnaryServerInterceptor {
|
||||
interceptor := grpc_recovery.UnaryServerInterceptor(grpc_recovery.WithRecoveryHandlerContext(recoveryFunc))
|
||||
return interceptor
|
||||
}
|
||||
|
||||
// NewStream returns a streaming server interceptor that adds telemetry to
|
||||
// streaming grpc calls.
|
||||
func NewStream() grpc.StreamServerInterceptor {
|
||||
interceptor := grpc_recovery.StreamServerInterceptor(grpc_recovery.WithRecoveryHandlerContext(recoveryFunc))
|
||||
return interceptor
|
||||
}
|
||||
|
||||
func recoveryFunc(ctx context.Context, p interface{}) (err error) {
|
||||
debug.PrintStack()
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Error().Msgf("%+v; stack: %s", p, debug.Stack())
|
||||
return status.Errorf(codes.Internal, "%s", p)
|
||||
}
|
||||
Generated
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
// 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 token
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// NewUnary returns a new unary interceptor that adds
|
||||
// the token to the context.
|
||||
func NewUnary() grpc.UnaryServerInterceptor {
|
||||
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if ok && md != nil {
|
||||
if val, ok := md[ctxpkg.TokenHeader]; ok {
|
||||
if len(val) > 0 && val[0] != "" {
|
||||
tkn := val[0]
|
||||
ctx = ctxpkg.ContextSetToken(ctx, tkn)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, tkn)
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := md[ctxpkg.InitiatorHeader]; ok {
|
||||
if len(val) > 0 && val[0] != "" {
|
||||
initiatorID := val[0]
|
||||
ctx = ctxpkg.ContextSetInitiator(ctx, initiatorID)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.InitiatorHeader, initiatorID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
}
|
||||
return interceptor
|
||||
}
|
||||
|
||||
// NewStream returns a new server stream interceptor
|
||||
// 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()
|
||||
|
||||
md, ok := metadata.FromIncomingContext(ss.Context())
|
||||
if ok && md != nil {
|
||||
if val, ok := md[ctxpkg.TokenHeader]; ok {
|
||||
if len(val) > 0 && val[0] != "" {
|
||||
tkn := val[0]
|
||||
ctx = ctxpkg.ContextSetToken(ctx, tkn)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, tkn)
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := md[ctxpkg.InitiatorHeader]; ok {
|
||||
if len(val) > 0 && val[0] != "" {
|
||||
initiatorID := val[0]
|
||||
ctx = ctxpkg.ContextSetInitiator(ctx, initiatorID)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.InitiatorHeader, initiatorID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Generated
Vendored
+70
@@ -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/opencloud-eu/reva/v2/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
|
||||
}
|
||||
Generated
Vendored
+162
@@ -0,0 +1,162 @@
|
||||
// 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 applicationauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
appauthpb "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appauth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appauth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("applicationauth", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf *config
|
||||
am appauth.Manager
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
appauthpb.RegisterApplicationsAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func getAppAuthManager(c *config) (appauth.Manager, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// New creates a app auth provider svc
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
|
||||
am, err := getAppAuthManager(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &service{
|
||||
conf: c,
|
||||
am: am,
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.auth.applications.v1beta1.ApplicationsAPI/GetAppPassword"}
|
||||
}
|
||||
|
||||
func (s *service) GenerateAppPassword(ctx context.Context, req *appauthpb.GenerateAppPasswordRequest) (*appauthpb.GenerateAppPasswordResponse, error) {
|
||||
logger := appctx.GetLogger(ctx)
|
||||
pwd, err := s.am.GenerateAppPassword(ctx, req.TokenScope, req.Label, req.Expiration)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("error generating app password")
|
||||
return &appauthpb.GenerateAppPasswordResponse{
|
||||
Status: status.NewInternal(ctx, "error generating app password"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &appauthpb.GenerateAppPasswordResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
AppPassword: pwd,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ListAppPasswords(ctx context.Context, req *appauthpb.ListAppPasswordsRequest) (*appauthpb.ListAppPasswordsResponse, error) {
|
||||
pwds, err := s.am.ListAppPasswords(ctx)
|
||||
if err != nil {
|
||||
return &appauthpb.ListAppPasswordsResponse{
|
||||
Status: status.NewInternal(ctx, "error listing app passwords"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &appauthpb.ListAppPasswordsResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
AppPasswords: pwds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) InvalidateAppPassword(ctx context.Context, req *appauthpb.InvalidateAppPasswordRequest) (*appauthpb.InvalidateAppPasswordResponse, error) {
|
||||
err := s.am.InvalidateAppPassword(ctx, req.Password)
|
||||
if err != nil {
|
||||
return &appauthpb.InvalidateAppPasswordResponse{
|
||||
Status: status.NewInternal(ctx, "error invalidating app password"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &appauthpb.InvalidateAppPasswordResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetAppPassword(ctx context.Context, req *appauthpb.GetAppPasswordRequest) (*appauthpb.GetAppPasswordResponse, error) {
|
||||
pwd, err := s.am.GetAppPassword(ctx, req.User, req.Password)
|
||||
if err != nil {
|
||||
return &appauthpb.GetAppPasswordResponse{
|
||||
Status: status.NewStatusFromErrType(ctx, "error getting app password via username/password", err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &appauthpb.GetAppPasswordResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
AppPassword: pwd,
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+208
@@ -0,0 +1,208 @@
|
||||
// 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 appprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
providerpb "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/juliangruber/go-intersect"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app/provider/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/logger"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("appprovider", New)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
provider app.Provider
|
||||
conf *config
|
||||
context context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
AppProviderURL string `mapstructure:"app_provider_url"`
|
||||
GatewaySvc string `mapstructure:"gatewaysvc"`
|
||||
MimeTypes []string `mapstructure:"mime_types"`
|
||||
Priority uint64 `mapstructure:"priority"`
|
||||
RefreshTime time.Duration `mapstructure:"refreshtime"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "demo"
|
||||
}
|
||||
if c.RefreshTime < 1 {
|
||||
c.RefreshTime = time.Second * 20
|
||||
}
|
||||
c.AppProviderURL = sharedconf.GetGatewaySVC(c.AppProviderURL)
|
||||
c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc)
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// New creates a new AppProviderService
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider, err := getProvider(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
|
||||
service := &service{
|
||||
conf: c,
|
||||
provider: provider,
|
||||
context: ctx,
|
||||
cancelFunc: cancelFunc,
|
||||
}
|
||||
|
||||
t := time.NewTicker(c.RefreshTime)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
service.registerProvider()
|
||||
case <-service.context.Done():
|
||||
t.Stop()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) registerProvider() {
|
||||
// Give the appregistry service time to come up
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
ctx := context.Background()
|
||||
log := logger.New().With().Int("pid", os.Getpid()).Logger()
|
||||
pInfo, err := s.provider.GetAppProviderInfo(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error registering app provider: could not get provider info")
|
||||
return
|
||||
}
|
||||
pInfo.Address = s.conf.AppProviderURL
|
||||
|
||||
if len(s.conf.MimeTypes) != 0 {
|
||||
mimeTypesIf := intersect.Simple(pInfo.MimeTypes, s.conf.MimeTypes)
|
||||
var mimeTypes []string
|
||||
for _, m := range mimeTypesIf {
|
||||
mimeTypes = append(mimeTypes, m.(string))
|
||||
}
|
||||
pInfo.MimeTypes = mimeTypes
|
||||
}
|
||||
|
||||
client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error registering app provider: could not get gateway client")
|
||||
return
|
||||
}
|
||||
req := ®istrypb.AddAppProviderRequest{Provider: pInfo}
|
||||
|
||||
if s.conf.Priority != 0 {
|
||||
req.Opaque = &types.Opaque{
|
||||
Map: map[string]*types.OpaqueEntry{
|
||||
"priority": {
|
||||
Decoder: "plain",
|
||||
Value: []byte(strconv.FormatUint(s.conf.Priority, 10)),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
res, err := client.AddAppProvider(ctx, req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error registering app provider: error calling add app provider")
|
||||
return
|
||||
}
|
||||
if res.Status.Code != rpc.Code_CODE_OK {
|
||||
err = status.NewErrorFromCode(res.Status.Code, "appprovider")
|
||||
log.Error().Err(err).Msg("error registering app provider: add app provider returned error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
providerpb.RegisterProviderAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func getProvider(c *config) (app.Provider, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
func (s *service) OpenInApp(ctx context.Context, req *providerpb.OpenInAppRequest) (*providerpb.OpenInAppResponse, error) {
|
||||
appURL, err := s.provider.GetAppURL(ctx, req.ResourceInfo, req.ViewMode, req.AccessToken, utils.ReadPlainFromOpaque(req.Opaque, "lang"))
|
||||
if err != nil {
|
||||
res := &providerpb.OpenInAppResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
res := &providerpb.OpenInAppResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
AppUrl: appURL,
|
||||
}
|
||||
return res, nil
|
||||
|
||||
}
|
||||
Generated
Vendored
+196
@@ -0,0 +1,196 @@
|
||||
// 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 appregistry
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/app/registry/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("appregistry", New)
|
||||
}
|
||||
|
||||
type svc struct {
|
||||
reg app.Registry
|
||||
}
|
||||
|
||||
func (s *svc) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *svc) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.app.registry.v1beta1.RegistryAPI/AddAppProvider", "/cs3.app.registry.v1beta1.RegistryAPI/ListSupportedMimeTypes"}
|
||||
}
|
||||
|
||||
func (s *svc) Register(ss *grpc.Server) {
|
||||
registrypb.RegisterRegistryAPIServer(ss, s)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "static"
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new StorageRegistryService
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reg, err := getRegistry(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := &svc{
|
||||
reg: reg,
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getRegistry(c *config) (app.Registry, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("appregistrysvc: driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
func (s *svc) GetAppProviders(ctx context.Context, req *registrypb.GetAppProvidersRequest) (*registrypb.GetAppProvidersResponse, error) {
|
||||
p, err := s.reg.FindProviders(ctx, req.ResourceInfo.MimeType)
|
||||
if err != nil {
|
||||
return ®istrypb.GetAppProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error looking for the app provider"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := ®istrypb.GetAppProvidersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Providers: p,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) AddAppProvider(ctx context.Context, req *registrypb.AddAppProviderRequest) (*registrypb.AddAppProviderResponse, error) {
|
||||
err := s.reg.AddProvider(ctx, req.Provider)
|
||||
if err != nil {
|
||||
return ®istrypb.AddAppProviderResponse{
|
||||
Status: status.NewInternal(ctx, "error adding the app provider"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := ®istrypb.AddAppProviderResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListAppProviders(ctx context.Context, req *registrypb.ListAppProvidersRequest) (*registrypb.ListAppProvidersResponse, error) {
|
||||
providers, err := s.reg.ListProviders(ctx)
|
||||
if err != nil {
|
||||
return ®istrypb.ListAppProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error listing the app providers"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := ®istrypb.ListAppProvidersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Providers: providers,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListSupportedMimeTypes(ctx context.Context, req *registrypb.ListSupportedMimeTypesRequest) (*registrypb.ListSupportedMimeTypesResponse, error) {
|
||||
mimeTypes, err := s.reg.ListSupportedMimeTypes(ctx)
|
||||
if err != nil {
|
||||
return ®istrypb.ListSupportedMimeTypesResponse{
|
||||
Status: status.NewInternal(ctx, "error listing the supported mime types"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// hide mimetypes for app providers
|
||||
for _, mime := range mimeTypes {
|
||||
for _, app := range mime.AppProviders {
|
||||
app.MimeTypes = nil
|
||||
}
|
||||
}
|
||||
|
||||
res := ®istrypb.ListSupportedMimeTypesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
MimeTypes: mimeTypes,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetDefaultAppProviderForMimeType(ctx context.Context, req *registrypb.GetDefaultAppProviderForMimeTypeRequest) (*registrypb.GetDefaultAppProviderForMimeTypeResponse, error) {
|
||||
provider, err := s.reg.GetDefaultProviderForMimeType(ctx, req.MimeType)
|
||||
if err != nil {
|
||||
return ®istrypb.GetDefaultAppProviderForMimeTypeResponse{
|
||||
Status: status.NewInternal(ctx, "error getting the default app provider for the mimetype"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := ®istrypb.GetDefaultAppProviderForMimeTypeResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Provider: provider,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) SetDefaultAppProviderForMimeType(ctx context.Context, req *registrypb.SetDefaultAppProviderForMimeTypeRequest) (*registrypb.SetDefaultAppProviderForMimeTypeResponse, error) {
|
||||
err := s.reg.SetDefaultProviderForMimeType(ctx, req.MimeType, req.Provider)
|
||||
if err != nil {
|
||||
return ®istrypb.SetDefaultAppProviderForMimeTypeResponse{
|
||||
Status: status.NewInternal(ctx, "error setting the default app provider for the mimetype"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := ®istrypb.SetDefaultAppProviderForMimeTypeResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+152
@@ -0,0 +1,152 @@
|
||||
// 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 authprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/plugin"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("authprovider", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
AuthManager string `mapstructure:"auth_manager"`
|
||||
AuthManagers map[string]map[string]interface{} `mapstructure:"auth_managers"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.AuthManager == "" {
|
||||
c.AuthManager = "json"
|
||||
}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
authmgr auth.Manager
|
||||
conf *config
|
||||
plugin *plugin.RevaPlugin
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getAuthManager(manager string, m map[string]map[string]interface{}) (auth.Manager, *plugin.RevaPlugin, error) {
|
||||
if manager == "" {
|
||||
return nil, nil, errtypes.InternalError("authsvc: driver not configured for auth manager")
|
||||
}
|
||||
p, err := plugin.Load("authprovider", manager)
|
||||
if err == nil {
|
||||
authManager, ok := p.Plugin.(auth.Manager)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("could not assert the loaded plugin")
|
||||
}
|
||||
pluginConfig := filepath.Base(manager)
|
||||
err = authManager.Configure(m[pluginConfig])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return authManager, p, nil
|
||||
} else if _, ok := err.(errtypes.NotFound); ok {
|
||||
if f, ok := registry.NewFuncs[manager]; ok {
|
||||
authmgr, err := f(m[manager])
|
||||
return authmgr, nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, nil, errtypes.NotFound(fmt.Sprintf("authsvc: driver %s not found for auth manager", manager))
|
||||
}
|
||||
|
||||
// New returns a new AuthProviderServiceServer.
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authManager, plug, err := getAuthManager(c.AuthManager, c.AuthManagers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := &service{
|
||||
conf: c,
|
||||
authmgr: authManager,
|
||||
plugin: plug,
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
if s.plugin != nil {
|
||||
s.plugin.Kill()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.auth.provider.v1beta1.ProviderAPI/Authenticate"}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
provider.RegisterProviderAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func (s *service) Authenticate(ctx context.Context, req *provider.AuthenticateRequest) (*provider.AuthenticateResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
username := req.ClientId
|
||||
password := req.ClientSecret
|
||||
|
||||
u, scope, err := s.authmgr.Authenticate(ctx, username, password)
|
||||
if err != nil {
|
||||
log.Debug().Str("client_id", username).Err(err).Msg("authsvc: error in Authenticate")
|
||||
return &provider.AuthenticateResponse{
|
||||
Status: status.NewStatusFromErrType(ctx, "authsvc: error in Authenticate", err),
|
||||
}, nil
|
||||
}
|
||||
log.Info().Msgf("user %s authenticated", u.Id)
|
||||
return &provider.AuthenticateResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: u,
|
||||
TokenScope: scope,
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+133
@@ -0,0 +1,133 @@
|
||||
// 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 authregistry
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/registry/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("authregistry", New)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
reg auth.Registry
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{
|
||||
"/cs3.auth.registry.v1beta1.RegistryAPI/GetAuthProvider",
|
||||
"/cs3.auth.registry.v1beta1.RegistryAPI/ListAuthProviders",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
registrypb.RegisterRegistryAPIServer(ss, s)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "static"
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new AuthRegistry
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.init()
|
||||
|
||||
reg, err := getRegistry(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &service{
|
||||
reg: reg,
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getRegistry(c *config) (auth.Registry, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("authregistrysvc: driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
func (s *service) ListAuthProviders(ctx context.Context, req *registrypb.ListAuthProvidersRequest) (*registrypb.ListAuthProvidersResponse, error) {
|
||||
pinfos, err := s.reg.ListProviders(ctx)
|
||||
if err != nil {
|
||||
return ®istrypb.ListAuthProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting list of auth providers"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := ®istrypb.ListAuthProvidersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Providers: pinfos,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) GetAuthProviders(ctx context.Context, req *registrypb.GetAuthProvidersRequest) (*registrypb.GetAuthProvidersResponse, error) {
|
||||
pinfo, err := s.reg.GetProvider(ctx, req.Type)
|
||||
if err != nil {
|
||||
return ®istrypb.GetAuthProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth provider for type: "+req.Type),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := ®istrypb.GetAuthProvidersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Providers: []*registrypb.ProviderInfo{pinfo},
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+381
@@ -0,0 +1,381 @@
|
||||
// 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 datatx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
txdriver "github.com/opencloud-eu/reva/v2/pkg/datatx"
|
||||
txregistry "github.com/opencloud-eu/reva/v2/pkg/datatx/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("datatx", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
// transfer driver
|
||||
TxDriver string `mapstructure:"txdriver"`
|
||||
TxDrivers map[string]map[string]interface{} `mapstructure:"txdrivers"`
|
||||
// storage driver to persist share/transfer relation
|
||||
StorageDriver string `mapstructure:"storage_driver"`
|
||||
StorageDrivers map[string]map[string]interface{} `mapstructure:"storage_drivers"`
|
||||
TxSharesFile string `mapstructure:"tx_shares_file"`
|
||||
DataTransfersFolder string `mapstructure:"data_transfers_folder"`
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf *config
|
||||
txManager txdriver.Manager
|
||||
txShareDriver *txShareDriver
|
||||
}
|
||||
|
||||
type txShareDriver struct {
|
||||
sync.Mutex // concurrent access to the file
|
||||
model *txShareModel
|
||||
}
|
||||
type txShareModel struct {
|
||||
File string
|
||||
TxShares map[string]*txShare `json:"shares"`
|
||||
}
|
||||
|
||||
type txShare struct {
|
||||
TxID string
|
||||
SrcTargetURI string
|
||||
DestTargetURI string
|
||||
Opaque *types.Opaque `json:"opaque"`
|
||||
}
|
||||
|
||||
type webdavEndpoint struct {
|
||||
filePath string
|
||||
endpoint string
|
||||
endpointScheme string
|
||||
token string
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.TxDriver == "" {
|
||||
c.TxDriver = "rclone"
|
||||
}
|
||||
if c.TxSharesFile == "" {
|
||||
c.TxSharesFile = "/var/tmp/reva/datatx-shares.json"
|
||||
}
|
||||
if c.DataTransfersFolder == "" {
|
||||
c.DataTransfersFolder = "/home/DataTransfers"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
datatx.RegisterTxAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func getDatatxManager(c *config) (txdriver.Manager, error) {
|
||||
if f, ok := txregistry.NewFuncs[c.TxDriver]; ok {
|
||||
return f(c.TxDrivers[c.TxDriver])
|
||||
}
|
||||
return nil, errtypes.NotFound("datatx service: driver not found: " + c.TxDriver)
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// New creates a new datatx svc
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
|
||||
txManager, err := getDatatxManager(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
model, err := loadOrCreate(c.TxSharesFile)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error loading the file containing the transfer shares")
|
||||
return nil, err
|
||||
}
|
||||
txShareDriver := &txShareDriver{
|
||||
model: model,
|
||||
}
|
||||
|
||||
service := &service{
|
||||
conf: c,
|
||||
txManager: txManager,
|
||||
txShareDriver: txShareDriver,
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *service) CreateTransfer(ctx context.Context, req *datatx.CreateTransferRequest) (*datatx.CreateTransferResponse, error) {
|
||||
srcEp, err := s.extractEndpointInfo(ctx, req.SrcTargetUri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcRemote := fmt.Sprintf("%s://%s", srcEp.endpointScheme, srcEp.endpoint)
|
||||
srcPath := srcEp.filePath
|
||||
srcToken := srcEp.token
|
||||
|
||||
destEp, err := s.extractEndpointInfo(ctx, req.DestTargetUri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dstRemote := fmt.Sprintf("%s://%s", destEp.endpointScheme, destEp.endpoint)
|
||||
dstPath := destEp.filePath
|
||||
dstToken := destEp.token
|
||||
|
||||
txInfo, startTransferErr := s.txManager.StartTransfer(ctx, srcRemote, srcPath, srcToken, dstRemote, dstPath, dstToken)
|
||||
|
||||
// we always save the transfer regardless of start transfer outcome
|
||||
// only then, if starting fails, can we try to restart it
|
||||
txShare := &txShare{
|
||||
TxID: txInfo.GetId().OpaqueId,
|
||||
SrcTargetURI: req.SrcTargetUri,
|
||||
DestTargetURI: req.DestTargetUri,
|
||||
Opaque: req.Opaque,
|
||||
}
|
||||
s.txShareDriver.Lock()
|
||||
defer s.txShareDriver.Unlock()
|
||||
|
||||
s.txShareDriver.model.TxShares[txInfo.GetId().OpaqueId] = txShare
|
||||
if err := s.txShareDriver.model.saveTxShare(); err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error saving transfer share: "+datatx.Status_STATUS_INVALID.String())
|
||||
return &datatx.CreateTransferResponse{
|
||||
Status: status.NewInvalid(ctx, "error pulling transfer"),
|
||||
}, err
|
||||
}
|
||||
|
||||
// now check start transfer outcome
|
||||
if startTransferErr != nil {
|
||||
startTransferErr = errors.Wrap(startTransferErr, "datatx service: error starting transfer job")
|
||||
return &datatx.CreateTransferResponse{
|
||||
Status: status.NewInvalid(ctx, "datatx service: error pulling transfer"),
|
||||
TxInfo: txInfo,
|
||||
}, startTransferErr
|
||||
}
|
||||
|
||||
return &datatx.CreateTransferResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
TxInfo: txInfo,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (s *service) GetTransferStatus(ctx context.Context, req *datatx.GetTransferStatusRequest) (*datatx.GetTransferStatusResponse, error) {
|
||||
txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()]
|
||||
if !ok {
|
||||
return nil, errtypes.InternalError("datatx service: transfer not found")
|
||||
}
|
||||
|
||||
txInfo, err := s.txManager.GetTransferStatus(ctx, req.GetTxId().OpaqueId)
|
||||
if err != nil {
|
||||
return &datatx.GetTransferStatusResponse{
|
||||
Status: status.NewInternal(ctx, "datatx service: error getting transfer status"),
|
||||
TxInfo: txInfo,
|
||||
}, err
|
||||
}
|
||||
|
||||
txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}
|
||||
|
||||
return &datatx.GetTransferStatusResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
TxInfo: txInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) CancelTransfer(ctx context.Context, req *datatx.CancelTransferRequest) (*datatx.CancelTransferResponse, error) {
|
||||
txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()]
|
||||
if !ok {
|
||||
return nil, errtypes.InternalError("datatx service: transfer not found")
|
||||
}
|
||||
|
||||
txInfo, err := s.txManager.CancelTransfer(ctx, req.GetTxId().OpaqueId)
|
||||
if err != nil {
|
||||
txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}
|
||||
return &datatx.CancelTransferResponse{
|
||||
Status: status.NewInternal(ctx, "error cancelling transfer"),
|
||||
TxInfo: txInfo,
|
||||
}, err
|
||||
}
|
||||
|
||||
txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}
|
||||
|
||||
return &datatx.CancelTransferResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
TxInfo: txInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ListTransfers(ctx context.Context, req *datatx.ListTransfersRequest) (*datatx.ListTransfersResponse, error) {
|
||||
filters := req.Filters
|
||||
var txInfos []*datatx.TxInfo
|
||||
for _, txShare := range s.txShareDriver.model.TxShares {
|
||||
if len(filters) == 0 {
|
||||
txInfos = append(txInfos, &datatx.TxInfo{
|
||||
Id: &datatx.TxId{OpaqueId: txShare.TxID},
|
||||
ShareId: &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)},
|
||||
})
|
||||
} else {
|
||||
for _, f := range filters {
|
||||
if f.Type == datatx.ListTransfersRequest_Filter_TYPE_SHARE_ID {
|
||||
if f.GetShareId().GetOpaqueId() == string(txShare.Opaque.Map["shareId"].Value) {
|
||||
txInfos = append(txInfos, &datatx.TxInfo{
|
||||
Id: &datatx.TxId{OpaqueId: txShare.TxID},
|
||||
ShareId: &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &datatx.ListTransfersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Transfers: txInfos,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) RetryTransfer(ctx context.Context, req *datatx.RetryTransferRequest) (*datatx.RetryTransferResponse, error) {
|
||||
txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()]
|
||||
if !ok {
|
||||
return nil, errtypes.InternalError("datatx service: transfer not found")
|
||||
}
|
||||
|
||||
txInfo, err := s.txManager.RetryTransfer(ctx, req.GetTxId().OpaqueId)
|
||||
if err != nil {
|
||||
return &datatx.RetryTransferResponse{
|
||||
Status: status.NewInternal(ctx, "error retrying transfer"),
|
||||
TxInfo: txInfo,
|
||||
}, err
|
||||
}
|
||||
|
||||
txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}
|
||||
|
||||
return &datatx.RetryTransferResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
TxInfo: txInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) extractEndpointInfo(ctx context.Context, targetURL string) (*webdavEndpoint, error) {
|
||||
if targetURL == "" {
|
||||
return nil, errtypes.BadRequest("datatx service: ref target is an empty uri")
|
||||
}
|
||||
|
||||
uri, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "datatx service: error parsing target uri: "+targetURL)
|
||||
}
|
||||
|
||||
m, err := url.ParseQuery(uri.RawQuery)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "datatx service: error parsing target resource name")
|
||||
}
|
||||
|
||||
return &webdavEndpoint{
|
||||
filePath: m["name"][0],
|
||||
endpoint: uri.Host + uri.Path,
|
||||
endpointScheme: uri.Scheme,
|
||||
token: uri.User.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func loadOrCreate(file string) (*txShareModel, error) {
|
||||
_, err := os.Stat(file)
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.WriteFile(file, []byte("{}"), 0700); err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error creating the transfer shares storage file: "+file)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
fd, err := os.OpenFile(file, os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error opening the transfer shares storage file: "+file)
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
data, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error reading the data")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
model := &txShareModel{}
|
||||
if err := json.Unmarshal(data, model); err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error decoding transfer shares data to json")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if model.TxShares == nil {
|
||||
model.TxShares = make(map[string]*txShare)
|
||||
}
|
||||
|
||||
model.File = file
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (m *txShareModel) saveTxShare() error {
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error encoding transfer share data to json")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(m.File, data, 0644); err != nil {
|
||||
err = errors.Wrap(err, "datatx service: error writing transfer share data to file: "+m.File)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
appauthpb "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) GenerateAppPassword(ctx context.Context, req *appauthpb.GenerateAppPasswordRequest) (*appauthpb.GenerateAppPasswordResponse, error) {
|
||||
c, err := pool.GetAppAuthProviderServiceClient(s.c.ApplicationAuthEndpoint)
|
||||
if err != nil {
|
||||
return &appauthpb.GenerateAppPasswordResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app auth provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GenerateAppPassword(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GenerateAppPassword")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListAppPasswords(ctx context.Context, req *appauthpb.ListAppPasswordsRequest) (*appauthpb.ListAppPasswordsResponse, error) {
|
||||
c, err := pool.GetAppAuthProviderServiceClient(s.c.ApplicationAuthEndpoint)
|
||||
if err != nil {
|
||||
return &appauthpb.ListAppPasswordsResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app auth provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListAppPasswords(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListAppPasswords")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) InvalidateAppPassword(ctx context.Context, req *appauthpb.InvalidateAppPasswordRequest) (*appauthpb.InvalidateAppPasswordResponse, error) {
|
||||
c, err := pool.GetAppAuthProviderServiceClient(s.c.ApplicationAuthEndpoint)
|
||||
if err != nil {
|
||||
return &appauthpb.InvalidateAppPasswordResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app auth provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.InvalidateAppPassword(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling InvalidateAppPassword")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetAppPassword(ctx context.Context, req *appauthpb.GetAppPasswordRequest) (*appauthpb.GetAppPasswordResponse, error) {
|
||||
c, err := pool.GetAppAuthProviderServiceClient(s.c.ApplicationAuthEndpoint)
|
||||
if err != nil {
|
||||
return &appauthpb.GetAppPasswordResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app auth provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetAppPassword(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetAppPassword")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+340
@@ -0,0 +1,340 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
providerpb "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/token"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func (s *svc) OpenInApp(ctx context.Context, req *gateway.OpenInAppRequest) (*providerpb.OpenInAppResponse, error) {
|
||||
statRes, err := s.Stat(ctx, &storageprovider.StatRequest{
|
||||
Ref: req.Ref,
|
||||
})
|
||||
if err != nil {
|
||||
return &providerpb.OpenInAppResponse{
|
||||
Status: status.NewInternal(ctx, "gateway: error calling Stat on the resource path for the app provider: "+req.Ref.GetPath()),
|
||||
}, nil
|
||||
}
|
||||
if statRes.Status.Code != rpc.Code_CODE_OK {
|
||||
return &providerpb.OpenInAppResponse{
|
||||
Status: statRes.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
fileInfo := statRes.Info
|
||||
|
||||
// The file is a share
|
||||
if fileInfo.Type == storageprovider.ResourceType_RESOURCE_TYPE_REFERENCE {
|
||||
uri, err := url.Parse(fileInfo.Target)
|
||||
if err != nil {
|
||||
return &providerpb.OpenInAppResponse{
|
||||
Status: status.NewInternal(ctx, "gateway: error parsing target uri: "+fileInfo.Target),
|
||||
}, nil
|
||||
}
|
||||
if uri.Scheme == "webdav" {
|
||||
insecure, skipVerify := getGRPCConfig(req.Opaque)
|
||||
return s.openFederatedShares(ctx, fileInfo.Target, req.ViewMode, req.App, insecure, skipVerify, "")
|
||||
}
|
||||
|
||||
res, err := s.Stat(ctx, &storageprovider.StatRequest{
|
||||
Ref: req.Ref,
|
||||
})
|
||||
if err != nil {
|
||||
return &providerpb.OpenInAppResponse{
|
||||
Status: status.NewInternal(ctx, "gateway: error calling Stat on the resource path for the app provider: "+req.Ref.GetPath()),
|
||||
}, nil
|
||||
}
|
||||
if res.Status.Code != rpc.Code_CODE_OK {
|
||||
return &providerpb.OpenInAppResponse{
|
||||
Status: status.NewInternal(ctx, "Stat failed on the resource path for the app provider: "+req.Ref.GetPath()),
|
||||
}, nil
|
||||
}
|
||||
fileInfo = res.Info
|
||||
}
|
||||
return s.openLocalResources(ctx, fileInfo, req.ViewMode, req.App, req.Opaque)
|
||||
}
|
||||
|
||||
func (s *svc) openFederatedShares(ctx context.Context, targetURL string, vm gateway.OpenInAppRequest_ViewMode, app string,
|
||||
insecure, skipVerify bool, nameQueries ...string) (*providerpb.OpenInAppResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
targetURL, err := appendNameQuery(targetURL, nameQueries...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ep, err := s.extractEndpointInfo(ctx, targetURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref := &storageprovider.Reference{Path: ep.filePath}
|
||||
appProviderReq := &gateway.OpenInAppRequest{
|
||||
Ref: ref,
|
||||
ViewMode: vm,
|
||||
App: app,
|
||||
}
|
||||
|
||||
meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{
|
||||
Domain: ep.endpoint,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetInfoByDomain")
|
||||
}
|
||||
var gatewayEP string
|
||||
for _, s := range meshProvider.ProviderInfo.Services {
|
||||
if strings.ToLower(s.Endpoint.Type.Name) == "gateway" {
|
||||
gatewayEP = s.Endpoint.Path
|
||||
}
|
||||
}
|
||||
log.Debug().Msgf("Forwarding OpenInApp request to: %s", gatewayEP)
|
||||
|
||||
conn, err := getConn(gatewayEP, insecure, skipVerify)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error connecting to remote reva")
|
||||
return nil, errors.Wrap(err, "gateway: error connecting to remote reva")
|
||||
}
|
||||
|
||||
gatewayClient := gateway.NewGatewayAPIClient(conn)
|
||||
remoteCtx := ctxpkg.ContextSetToken(context.Background(), ep.token)
|
||||
remoteCtx = metadata.AppendToOutgoingContext(remoteCtx, ctxpkg.TokenHeader, ep.token)
|
||||
|
||||
res, err := gatewayClient.OpenInApp(remoteCtx, appProviderReq)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error returned by remote OpenInApp call")
|
||||
return nil, errors.Wrap(err, "gateway: error calling OpenInApp")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) openLocalResources(ctx context.Context, ri *storageprovider.ResourceInfo,
|
||||
vm gateway.OpenInAppRequest_ViewMode, app string, opaque *typespb.Opaque) (*providerpb.OpenInAppResponse, error) {
|
||||
|
||||
accessToken, ok := ctxpkg.ContextGetToken(ctx)
|
||||
if !ok || accessToken == "" {
|
||||
return &providerpb.OpenInAppResponse{
|
||||
Status: status.NewUnauthenticated(ctx, errtypes.InvalidCredentials("Access token is invalid or empty"), ""),
|
||||
}, nil
|
||||
}
|
||||
|
||||
provider, err := s.findAppProvider(ctx, ri, app)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "gateway: error calling findAppProvider")
|
||||
if _, ok := err.(errtypes.IsNotFound); ok {
|
||||
return &providerpb.OpenInAppResponse{
|
||||
Status: status.NewNotFound(ctx, "Could not find the requested app provider"),
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appProviderClient, err := pool.GetAppProviderClient(provider.Address)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetAppProviderClient")
|
||||
}
|
||||
|
||||
appProviderReq, err := buildOpenInAppRequest(ctx, ri, vm, s.tokenmgr, accessToken, opaque)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error building OpenInApp request")
|
||||
}
|
||||
|
||||
res, err := appProviderClient.OpenInApp(ctx, appProviderReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling OpenInApp")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func buildOpenInAppRequest(ctx context.Context, ri *storageprovider.ResourceInfo, vm gateway.OpenInAppRequest_ViewMode, tokenmgr token.Manager, accessToken string, opaque *typespb.Opaque) (*providerpb.OpenInAppRequest, error) {
|
||||
// in case of a view only mode and a stat permission we need to create a view only token
|
||||
if vm == gateway.OpenInAppRequest_VIEW_MODE_VIEW_ONLY && ri.GetPermissionSet().GetStat() {
|
||||
// Limit scope to the resource
|
||||
scope, err := scope.AddResourceInfoScope(ri, providerv1beta1.Role_ROLE_VIEWER, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// build a fake user object for the token
|
||||
currentuser, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("user not found in context")
|
||||
}
|
||||
|
||||
scopedUser := &userpb.User{
|
||||
Id: ri.GetOwner(), // the owner of the resource is always set, right?
|
||||
DisplayName: "View Only user for " + currentuser.GetUsername(),
|
||||
}
|
||||
|
||||
// mint a view only token
|
||||
viewOnlyToken, err := tokenmgr.MintToken(ctx, scopedUser, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO we should not append the token to the opaque, we should have a dedicated field in the request
|
||||
opaque = utils.AppendPlainToOpaque(opaque, "viewOnlyToken", viewOnlyToken)
|
||||
}
|
||||
|
||||
return &providerpb.OpenInAppRequest{
|
||||
ResourceInfo: ri,
|
||||
ViewMode: providerpb.ViewMode(vm),
|
||||
AccessToken: accessToken,
|
||||
// ViewOnlyToken: viewOnlyToken // scoped to the shared resource if the stat response hase a ViewOnly permission
|
||||
Opaque: opaque,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *svc) findAppProvider(ctx context.Context, ri *storageprovider.ResourceInfo, app string) (*registry.ProviderInfo, error) {
|
||||
c, err := pool.GetAppRegistryClient(s.c.AppRegistryEndpoint)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "gateway: error getting appregistry client")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// when app is empty it means the user assumes a default behaviour.
|
||||
// From a web perspective, means the user click on the file itself.
|
||||
// Normally the file will get downloaded but if a suitable application exists
|
||||
// the behaviour will change from download to open the file with the app.
|
||||
if app == "" {
|
||||
// If app is empty means that we need to rely on "default" behaviour.
|
||||
// We currently do not have user preferences implemented so the only default
|
||||
// we can currently enforce is one configured by the system admins, the
|
||||
// "system default".
|
||||
// If a default is not set we raise an error rather that giving the user the first provider in the list
|
||||
// as the list is built on init time and is not deterministic, giving the user different results on service
|
||||
// reload.
|
||||
res, err := c.GetDefaultAppProviderForMimeType(ctx, ®istry.GetDefaultAppProviderForMimeTypeRequest{
|
||||
MimeType: ri.MimeType,
|
||||
})
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "gateway: error calling GetDefaultAppProviderForMimeType")
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
// we've found a provider
|
||||
if res.Status.Code == rpc.Code_CODE_OK && res.Provider != nil {
|
||||
return res.Provider, nil
|
||||
}
|
||||
|
||||
// we did not find a default provider
|
||||
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
|
||||
err := errtypes.NotFound(fmt.Sprintf("gateway: default app provider for mime type:%s not found", ri.MimeType))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// response code is something else, bubble up error
|
||||
// if a default is not set we abort as returning the first application available is not
|
||||
// deterministic for the end-user as it depends on initialization order of the app approviders with the registry.
|
||||
// It also provides a good hint to the system admin to configure the defaults accordingly.
|
||||
err = errtypes.InternalError(fmt.Sprintf("gateway: unexpected grpc response status:%s when calling GetDefaultAppProviderForMimeType", res.Status))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// app has been forced and is set, we try to get an app provider that can satisfy it
|
||||
// Note that we ask for the list of all available providers for a given resource
|
||||
// even though we're only interested into the one set by the "app" parameter.
|
||||
// A better call will be to issue a (to be added) GetAppProviderByName(app) method
|
||||
// to just get what we ask for.
|
||||
res, err := c.GetAppProviders(ctx, ®istry.GetAppProvidersRequest{
|
||||
ResourceInfo: ri,
|
||||
})
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "gateway: error calling GetAppProviders")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if the list of app providers is empty means we expect a CODE_NOT_FOUND in the response
|
||||
if res.Status.Code != rpc.Code_CODE_OK {
|
||||
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
|
||||
return nil, errtypes.NotFound("gateway: app provider not found for resource: " + ri.String())
|
||||
}
|
||||
return nil, errtypes.InternalError("gateway: error finding app providers")
|
||||
}
|
||||
|
||||
// as long as the above mentioned GetAppProviderByName(app) method is not available
|
||||
// we need to apply a manual filter
|
||||
filteredProviders := []*registry.ProviderInfo{}
|
||||
for _, p := range res.Providers {
|
||||
if p.Name == app {
|
||||
filteredProviders = append(filteredProviders, p)
|
||||
}
|
||||
}
|
||||
res.Providers = filteredProviders
|
||||
|
||||
if len(res.Providers) == 0 {
|
||||
return nil, errtypes.NotFound(fmt.Sprintf("app '%s' not found", app))
|
||||
}
|
||||
|
||||
if len(res.Providers) == 1 {
|
||||
return res.Providers[0], nil
|
||||
}
|
||||
|
||||
// we should never arrive to the point of having more than one
|
||||
// provider for the given "app" parameters sent by the user
|
||||
return nil, errtypes.InternalError(fmt.Sprintf("gateway: user requested app %q and we provided %d applications", app, len(res.Providers)))
|
||||
|
||||
}
|
||||
|
||||
func getGRPCConfig(opaque *typespb.Opaque) (bool, bool) {
|
||||
if opaque == nil {
|
||||
return false, false
|
||||
}
|
||||
_, insecure := opaque.Map["insecure"]
|
||||
_, skipVerify := opaque.Map["skip-verify"]
|
||||
return insecure, skipVerify
|
||||
}
|
||||
|
||||
func getConn(host string, ins, skipverify bool) (*grpc.ClientConn, error) {
|
||||
if ins {
|
||||
return grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
}
|
||||
|
||||
// TODO(labkode): if in the future we want client-side certificate validation,
|
||||
// we need to load the client cert here
|
||||
tlsconf := &tls.Config{InsecureSkipVerify: skipverify}
|
||||
creds := credentials.NewTLS(tlsconf)
|
||||
return grpc.NewClient(host, grpc.WithTransportCredentials(creds))
|
||||
}
|
||||
Generated
Vendored
+124
@@ -0,0 +1,124 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) GetAppProviders(ctx context.Context, req *registry.GetAppProvidersRequest) (*registry.GetAppProvidersResponse, error) {
|
||||
c, err := pool.GetAppRegistryClient(s.c.AppRegistryEndpoint)
|
||||
if err != nil {
|
||||
return ®istry.GetAppProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app registry client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetAppProviders(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetAppProviders")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) AddAppProvider(ctx context.Context, req *registry.AddAppProviderRequest) (*registry.AddAppProviderResponse, error) {
|
||||
c, err := pool.GetAppRegistryClient(s.c.AppRegistryEndpoint)
|
||||
if err != nil {
|
||||
return ®istry.AddAppProviderResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app registry client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.AddAppProvider(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling AddAppProvider")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListAppProviders(ctx context.Context, req *registry.ListAppProvidersRequest) (*registry.ListAppProvidersResponse, error) {
|
||||
c, err := pool.GetAppRegistryClient(s.c.AppRegistryEndpoint)
|
||||
if err != nil {
|
||||
return ®istry.ListAppProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app registry client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListAppProviders(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListAppProviders")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListSupportedMimeTypes(ctx context.Context, req *registry.ListSupportedMimeTypesRequest) (*registry.ListSupportedMimeTypesResponse, error) {
|
||||
c, err := pool.GetAppRegistryClient(s.c.AppRegistryEndpoint)
|
||||
if err != nil {
|
||||
return ®istry.ListSupportedMimeTypesResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app registry client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListSupportedMimeTypes(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListSupportedMimeTypes")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetDefaultAppProviderForMimeType(ctx context.Context, req *registry.GetDefaultAppProviderForMimeTypeRequest) (*registry.GetDefaultAppProviderForMimeTypeResponse, error) {
|
||||
c, err := pool.GetAppRegistryClient(s.c.AppRegistryEndpoint)
|
||||
if err != nil {
|
||||
return ®istry.GetDefaultAppProviderForMimeTypeResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app registry client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetDefaultAppProviderForMimeType(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetDefaultAppProviderForMimeType")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) SetDefaultAppProviderForMimeType(ctx context.Context, req *registry.SetDefaultAppProviderForMimeTypeRequest) (*registry.SetDefaultAppProviderForMimeTypeResponse, error) {
|
||||
c, err := pool.GetAppRegistryClient(s.c.AppRegistryEndpoint)
|
||||
if err != nil {
|
||||
return ®istry.SetDefaultAppProviderForMimeTypeResponse{
|
||||
Status: status.NewInternal(ctx, "error getting app registry client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.SetDefaultAppProviderForMimeType(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling SetDefaultAppProviderForMimeType")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+206
@@ -0,0 +1,206 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest) (*gateway.AuthenticateResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
// find auth provider
|
||||
c, err := s.findAuthProvider(ctx, req.Type)
|
||||
if err != nil {
|
||||
log.Err(err).Str("type", req.Type).Msg("error getting auth provider client")
|
||||
return &gateway.AuthenticateResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
authProviderReq := &authpb.AuthenticateRequest{
|
||||
ClientId: req.ClientId,
|
||||
ClientSecret: req.ClientSecret,
|
||||
}
|
||||
res, err := c.Authenticate(ctx, authProviderReq)
|
||||
switch {
|
||||
case err != nil:
|
||||
return &gateway.AuthenticateResponse{
|
||||
Status: status.NewInternal(ctx, fmt.Sprintf("gateway: error calling Authenticate for type: %s", req.Type)),
|
||||
}, nil
|
||||
case res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED:
|
||||
fallthrough
|
||||
case res.Status.Code == rpc.Code_CODE_UNAUTHENTICATED:
|
||||
fallthrough
|
||||
case res.Status.Code == rpc.Code_CODE_NOT_FOUND:
|
||||
// normal failures, no need to log
|
||||
return &gateway.AuthenticateResponse{
|
||||
Status: res.Status,
|
||||
}, nil
|
||||
case res.Status.Code != rpc.Code_CODE_OK:
|
||||
return &gateway.AuthenticateResponse{
|
||||
Status: status.NewInternal(ctx, fmt.Sprintf("error authenticating credentials to auth provider for type: %s", req.Type)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validate valid userId
|
||||
if res.User == nil {
|
||||
err := errtypes.NotFound("gateway: user after Authenticate is nil")
|
||||
log.Err(err).Msg("user is nil")
|
||||
return &gateway.AuthenticateResponse{
|
||||
Status: status.NewInternal(ctx, "user is nil"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if res.User.Id == nil {
|
||||
err := errtypes.NotFound("gateway: uid after Authenticate is nil")
|
||||
log.Err(err).Msg("user id is nil")
|
||||
return &gateway.AuthenticateResponse{
|
||||
Status: status.NewInternal(ctx, "user id is nil"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
u := proto.Clone(res.User).(*userpb.User)
|
||||
if sharedconf.SkipUserGroupsInToken() {
|
||||
u.Groups = []string{}
|
||||
}
|
||||
|
||||
token, err := s.tokenmgr.MintToken(ctx, u, res.TokenScope)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "authsvc: error in MintToken")
|
||||
res := &gateway.AuthenticateResponse{
|
||||
Status: status.NewUnauthenticated(ctx, err, "error creating access token"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if scope, ok := res.TokenScope["user"]; s.c.DisableHomeCreationOnLogin || !ok || scope.Role != authpb.Role_ROLE_OWNER || res.User.Id.Type == userpb.UserType_USER_TYPE_FEDERATED {
|
||||
gwRes := &gateway.AuthenticateResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: res.User,
|
||||
Token: token,
|
||||
}
|
||||
return gwRes, nil
|
||||
}
|
||||
|
||||
// we need to pass the token to authenticate the CreateHome request.
|
||||
// TODO(labkode): appending to existing context will not pass the token.
|
||||
ctx = ctxpkg.ContextSetToken(ctx, token)
|
||||
ctx = ctxpkg.ContextSetUser(ctx, res.User)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token) // TODO(jfd): hardcoded metadata key. use PerRPCCredentials?
|
||||
|
||||
// create home directory
|
||||
createHomeRes, err := s.CreateHome(ctx, &storageprovider.CreateHomeRequest{})
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error calling CreateHome")
|
||||
return &gateway.AuthenticateResponse{
|
||||
Status: status.NewInternal(ctx, "error creating user home"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if createHomeRes.Status.Code != rpc.Code_CODE_OK && createHomeRes.Status.Code != rpc.Code_CODE_ALREADY_EXISTS {
|
||||
err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway")
|
||||
log.Err(err).Msg("error calling Createhome")
|
||||
return &gateway.AuthenticateResponse{
|
||||
Status: status.NewInternal(ctx, "error creating user home"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
gwRes := &gateway.AuthenticateResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: res.User,
|
||||
Token: token,
|
||||
}
|
||||
return gwRes, nil
|
||||
}
|
||||
|
||||
func (s *svc) WhoAmI(ctx context.Context, req *gateway.WhoAmIRequest) (*gateway.WhoAmIResponse, error) {
|
||||
u, _, err := s.tokenmgr.DismantleToken(ctx, req.Token)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "gateway: error getting user from token")
|
||||
return &gateway.WhoAmIResponse{
|
||||
Status: status.NewUnauthenticated(ctx, err, "error dismantling token"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if sharedconf.SkipUserGroupsInToken() {
|
||||
groupsRes, err := s.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: u.Id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Groups = groupsRes.Groups
|
||||
}
|
||||
|
||||
res := &gateway.WhoAmIResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: u,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) findAuthProvider(ctx context.Context, authType string) (authpb.ProviderAPIClient, error) {
|
||||
c, err := pool.GetAuthRegistryServiceClient(s.c.AuthRegistryEndpoint)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "gateway: error getting auth registry client")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.GetAuthProviders(ctx, ®istry.GetAuthProvidersRequest{
|
||||
Type: authType,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "gateway: error calling GetAuthProvider")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.Status.Code == rpc.Code_CODE_OK && res.Providers != nil && len(res.Providers) > 0 {
|
||||
// TODO(labkode): check for capabilities here
|
||||
c, err := pool.GetAuthProviderServiceClient(res.Providers[0].Address)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "gateway: error getting an auth provider client")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
|
||||
return nil, errtypes.NotFound("gateway: auth provider not found for type:" + authType)
|
||||
}
|
||||
|
||||
return nil, errtypes.InternalError("gateway: error finding an auth provider for type: " + authType)
|
||||
}
|
||||
Generated
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1"
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
)
|
||||
|
||||
func (s *svc) ListAuthProviders(ctx context.Context, req *registry.ListAuthProvidersRequest) (*gateway.ListAuthProvidersResponse, error) {
|
||||
c, err := pool.GetAuthRegistryServiceClient(s.c.AuthRegistryEndpoint)
|
||||
if err != nil {
|
||||
return &gateway.ListAuthProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "gateway"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListAuthProviders(ctx, req)
|
||||
if err != nil {
|
||||
return &gateway.ListAuthProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "gateway"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if res.Status.Code != rpc.Code_CODE_OK {
|
||||
return &gateway.ListAuthProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "gateway"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
types := make([]string, len(res.Providers))
|
||||
for i, p := range res.Providers {
|
||||
types[i] = p.ProviderType
|
||||
}
|
||||
|
||||
gwRes := &gateway.ListAuthProvidersResponse{
|
||||
Status: res.Status,
|
||||
Opaque: res.Opaque,
|
||||
Types: types,
|
||||
}
|
||||
|
||||
return gwRes, nil
|
||||
}
|
||||
Generated
Vendored
+108
@@ -0,0 +1,108 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) CreateTransfer(ctx context.Context, req *datatx.CreateTransferRequest) (*datatx.CreateTransferResponse, error) {
|
||||
c, err := pool.GetDataTxClient(s.c.DataTxEndpoint)
|
||||
if err != nil {
|
||||
return &datatx.CreateTransferResponse{
|
||||
Status: status.NewInternal(ctx, "error getting data transfer client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.CreateTransfer(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling PullTransfer")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetTransferStatus(ctx context.Context, req *datatx.GetTransferStatusRequest) (*datatx.GetTransferStatusResponse, error) {
|
||||
c, err := pool.GetDataTxClient(s.c.DataTxEndpoint)
|
||||
if err != nil {
|
||||
return &datatx.GetTransferStatusResponse{
|
||||
Status: status.NewInternal(ctx, "error getting data transfer client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetTransferStatus(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetTransferStatus")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) CancelTransfer(ctx context.Context, req *datatx.CancelTransferRequest) (*datatx.CancelTransferResponse, error) {
|
||||
c, err := pool.GetDataTxClient(s.c.DataTxEndpoint)
|
||||
if err != nil {
|
||||
return &datatx.CancelTransferResponse{
|
||||
Status: status.NewInternal(ctx, "error getting data transfer client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.CancelTransfer(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling CancelTransfer")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListTransfers(ctx context.Context, req *datatx.ListTransfersRequest) (*datatx.ListTransfersResponse, error) {
|
||||
c, err := pool.GetDataTxClient(s.c.DataTxEndpoint)
|
||||
if err != nil {
|
||||
return &datatx.ListTransfersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting data transfer client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListTransfers(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListTransfers")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) RetryTransfer(ctx context.Context, req *datatx.RetryTransferRequest) (*datatx.RetryTransferResponse, error) {
|
||||
c, err := pool.GetDataTxClient(s.c.DataTxEndpoint)
|
||||
if err != nil {
|
||||
return &datatx.RetryTransferResponse{
|
||||
Status: status.NewInternal(ctx, "error getting data transfer client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.RetryTransfer(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling RetryTransfer")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+213
@@ -0,0 +1,213 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/cache"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/token"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/token/manager/registry"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("gateway", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
AuthRegistryEndpoint string `mapstructure:"authregistrysvc"`
|
||||
ApplicationAuthEndpoint string `mapstructure:"applicationauthsvc"`
|
||||
StorageRegistryEndpoint string `mapstructure:"storageregistrysvc"`
|
||||
AppRegistryEndpoint string `mapstructure:"appregistrysvc"`
|
||||
PreferencesEndpoint string `mapstructure:"preferencessvc"`
|
||||
UserShareProviderEndpoint string `mapstructure:"usershareprovidersvc"`
|
||||
PublicShareProviderEndpoint string `mapstructure:"publicshareprovidersvc"`
|
||||
OCMShareProviderEndpoint string `mapstructure:"ocmshareprovidersvc"`
|
||||
OCMInviteManagerEndpoint string `mapstructure:"ocminvitemanagersvc"`
|
||||
OCMProviderAuthorizerEndpoint string `mapstructure:"ocmproviderauthorizersvc"`
|
||||
OCMCoreEndpoint string `mapstructure:"ocmcoresvc"`
|
||||
UserProviderEndpoint string `mapstructure:"userprovidersvc"`
|
||||
GroupProviderEndpoint string `mapstructure:"groupprovidersvc"`
|
||||
TenantProviderEndpoint string `mapstructure:"tenantprovidersvc"`
|
||||
DataTxEndpoint string `mapstructure:"datatx"`
|
||||
DataGatewayEndpoint string `mapstructure:"datagateway"`
|
||||
PermissionsEndpoint string `mapstructure:"permissionssvc"`
|
||||
CommitShareToStorageGrant bool `mapstructure:"commit_share_to_storage_grant"`
|
||||
DisableHomeCreationOnLogin bool `mapstructure:"disable_home_creation_on_login"`
|
||||
TransferSharedSecret string `mapstructure:"transfer_shared_secret"`
|
||||
TransferExpires int64 `mapstructure:"transfer_expires"`
|
||||
TokenManager string `mapstructure:"token_manager"`
|
||||
// ShareFolder is the location where to create shares in the recipient's storage provider.
|
||||
// FIXME get rid of ShareFolder, there are findByPath calls in the ocmshareporvider.go and usershareprovider.go
|
||||
ShareFolder string `mapstructure:"share_folder"`
|
||||
DataTransfersFolder string `mapstructure:"data_transfers_folder"`
|
||||
TokenManagers map[string]map[string]interface{} `mapstructure:"token_managers"`
|
||||
AllowedUserAgents map[string][]string `mapstructure:"allowed_user_agents"` // map[path][]user-agent
|
||||
CreatePersonalSpaceCacheConfig cache.Config `mapstructure:"create_personal_space_cache_config"`
|
||||
ProviderCacheConfig cache.Config `mapstructure:"provider_cache_config"`
|
||||
}
|
||||
|
||||
// sets defaults
|
||||
func (c *config) init() {
|
||||
if c.ShareFolder == "" {
|
||||
c.ShareFolder = "MyShares"
|
||||
}
|
||||
|
||||
c.ShareFolder = strings.Trim(c.ShareFolder, "/")
|
||||
|
||||
if c.DataTransfersFolder == "" {
|
||||
c.DataTransfersFolder = "DataTransfers"
|
||||
}
|
||||
|
||||
if c.TokenManager == "" {
|
||||
c.TokenManager = "jwt"
|
||||
}
|
||||
|
||||
// if services address are not specified we used the shared conf
|
||||
// for the gatewaysvc to have dev setups very quickly.
|
||||
c.AuthRegistryEndpoint = sharedconf.GetGatewaySVC(c.AuthRegistryEndpoint)
|
||||
c.ApplicationAuthEndpoint = sharedconf.GetGatewaySVC(c.ApplicationAuthEndpoint)
|
||||
c.StorageRegistryEndpoint = sharedconf.GetGatewaySVC(c.StorageRegistryEndpoint)
|
||||
c.AppRegistryEndpoint = sharedconf.GetGatewaySVC(c.AppRegistryEndpoint)
|
||||
c.PreferencesEndpoint = sharedconf.GetGatewaySVC(c.PreferencesEndpoint)
|
||||
c.UserShareProviderEndpoint = sharedconf.GetGatewaySVC(c.UserShareProviderEndpoint)
|
||||
c.PublicShareProviderEndpoint = sharedconf.GetGatewaySVC(c.PublicShareProviderEndpoint)
|
||||
c.OCMShareProviderEndpoint = sharedconf.GetGatewaySVC(c.OCMShareProviderEndpoint)
|
||||
c.OCMInviteManagerEndpoint = sharedconf.GetGatewaySVC(c.OCMInviteManagerEndpoint)
|
||||
c.OCMProviderAuthorizerEndpoint = sharedconf.GetGatewaySVC(c.OCMProviderAuthorizerEndpoint)
|
||||
c.OCMCoreEndpoint = sharedconf.GetGatewaySVC(c.OCMCoreEndpoint)
|
||||
c.UserProviderEndpoint = sharedconf.GetGatewaySVC(c.UserProviderEndpoint)
|
||||
c.GroupProviderEndpoint = sharedconf.GetGatewaySVC(c.GroupProviderEndpoint)
|
||||
// Fall back to userprovidersvc when no dedicated tenant provider is configured.
|
||||
if c.TenantProviderEndpoint == "" {
|
||||
c.TenantProviderEndpoint = c.UserProviderEndpoint
|
||||
} else {
|
||||
c.TenantProviderEndpoint = sharedconf.GetGatewaySVC(c.TenantProviderEndpoint)
|
||||
}
|
||||
c.DataTxEndpoint = sharedconf.GetGatewaySVC(c.DataTxEndpoint)
|
||||
|
||||
c.DataGatewayEndpoint = sharedconf.GetDataGateway(c.DataGatewayEndpoint)
|
||||
|
||||
// use shared secret if not set
|
||||
c.TransferSharedSecret = sharedconf.GetJWTSecret(c.TransferSharedSecret)
|
||||
|
||||
// lifetime for the transfer token (TUS upload)
|
||||
if c.TransferExpires == 0 {
|
||||
c.TransferExpires = 100 * 60 // seconds
|
||||
}
|
||||
|
||||
// caching needs to be explicitly enabled
|
||||
if c.ProviderCacheConfig.Store == "" {
|
||||
c.ProviderCacheConfig.Store = "noop"
|
||||
}
|
||||
|
||||
if c.ProviderCacheConfig.Database == "" {
|
||||
c.ProviderCacheConfig.Database = "reva"
|
||||
}
|
||||
|
||||
if c.CreatePersonalSpaceCacheConfig.Store == "" {
|
||||
c.CreatePersonalSpaceCacheConfig.Store = "memory"
|
||||
}
|
||||
|
||||
if c.CreatePersonalSpaceCacheConfig.Database == "" {
|
||||
c.CreatePersonalSpaceCacheConfig.Database = "reva"
|
||||
}
|
||||
}
|
||||
|
||||
type svc struct {
|
||||
c *config
|
||||
dataGatewayURL url.URL
|
||||
tokenmgr token.Manager
|
||||
providerCache cache.ProviderCache
|
||||
createPersonalSpaceCache cache.CreatePersonalSpaceCache
|
||||
}
|
||||
|
||||
// New creates a new gateway svc that acts as a proxy for any grpc operation.
|
||||
// The gateway is responsible for high-level controls: rate-limiting, coordination between svcs
|
||||
// like sharing and storage acls, asynchronous transactions, ...
|
||||
func New(m map[string]interface{}, _ *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.init()
|
||||
|
||||
// ensure DataGatewayEndpoint is a valid URI
|
||||
u, err := url.Parse(c.DataGatewayEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenManager, err := getTokenManager(c.TokenManager, c.TokenManagers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &svc{
|
||||
c: c,
|
||||
dataGatewayURL: *u,
|
||||
tokenmgr: tokenManager,
|
||||
providerCache: cache.GetProviderCache(c.ProviderCacheConfig),
|
||||
createPersonalSpaceCache: cache.GetCreatePersonalSpaceCache(c.CreatePersonalSpaceCacheConfig),
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *svc) Register(ss *grpc.Server) {
|
||||
gateway.RegisterGatewayAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func (s *svc) Close() error {
|
||||
s.providerCache.Close()
|
||||
s.createPersonalSpaceCache.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *svc) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.gateway.v1beta1.GatewayAPI"}
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "gateway: error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getTokenManager(manager string, m map[string]map[string]interface{}) (token.Manager, error) {
|
||||
if f, ok := registry.NewFuncs[manager]; ok {
|
||||
return f(m[manager])
|
||||
}
|
||||
|
||||
return nil, errtypes.NotFound(fmt.Sprintf("driver %s not found for token manager", manager))
|
||||
}
|
||||
Generated
Vendored
+108
@@ -0,0 +1,108 @@
|
||||
// Copyright 2018-2020 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) GetGroup(ctx context.Context, req *group.GetGroupRequest) (*group.GetGroupResponse, error) {
|
||||
c, err := pool.GetGroupProviderServiceClient(s.c.GroupProviderEndpoint)
|
||||
if err != nil {
|
||||
return &group.GetGroupResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetGroup(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetGroup")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetGroupByClaim(ctx context.Context, req *group.GetGroupByClaimRequest) (*group.GetGroupByClaimResponse, error) {
|
||||
c, err := pool.GetGroupProviderServiceClient(s.c.GroupProviderEndpoint)
|
||||
if err != nil {
|
||||
return &group.GetGroupByClaimResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetGroupByClaim(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetGroupByClaim")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) FindGroups(ctx context.Context, req *group.FindGroupsRequest) (*group.FindGroupsResponse, error) {
|
||||
c, err := pool.GetGroupProviderServiceClient(s.c.GroupProviderEndpoint)
|
||||
if err != nil {
|
||||
return &group.FindGroupsResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.FindGroups(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling FindGroups")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetMembers(ctx context.Context, req *group.GetMembersRequest) (*group.GetMembersResponse, error) {
|
||||
c, err := pool.GetGroupProviderServiceClient(s.c.GroupProviderEndpoint)
|
||||
if err != nil {
|
||||
return &group.GetMembersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetMembers(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetMembers")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) HasMember(ctx context.Context, req *group.HasMemberRequest) (*group.HasMemberResponse, error) {
|
||||
c, err := pool.GetGroupProviderServiceClient(s.c.GroupProviderEndpoint)
|
||||
if err != nil {
|
||||
return &group.HasMemberResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.HasMember(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling HasMember")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
// Copyright 2018-2023 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCMCoreShareRequest) (*ocmcore.CreateOCMCoreShareResponse, error) {
|
||||
c, err := pool.GetOCMCoreClient(s.c.OCMCoreEndpoint)
|
||||
if err != nil {
|
||||
return &ocmcore.CreateOCMCoreShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting ocm core client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.CreateOCMCoreShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling CreateOCMCoreShare")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) UpdateOCMCoreShare(ctx context.Context, req *ocmcore.UpdateOCMCoreShareRequest) (*ocmcore.UpdateOCMCoreShareResponse, error) {
|
||||
c, err := pool.GetOCMCoreClient(s.c.OCMCoreEndpoint)
|
||||
if err != nil {
|
||||
return &ocmcore.UpdateOCMCoreShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting ocm core client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.UpdateOCMCoreShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling UpdateOCMCoreShare")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) DeleteOCMCoreShare(ctx context.Context, req *ocmcore.DeleteOCMCoreShareRequest) (*ocmcore.DeleteOCMCoreShareResponse, error) {
|
||||
c, err := pool.GetOCMCoreClient(s.c.OCMCoreEndpoint)
|
||||
if err != nil {
|
||||
return &ocmcore.DeleteOCMCoreShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting ocm core client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.DeleteOCMCoreShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling UpdateOCMCoreShare")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+140
@@ -0,0 +1,140 @@
|
||||
// Copyright 2018-2023 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) GenerateInviteToken(ctx context.Context, req *invitepb.GenerateInviteTokenRequest) (*invitepb.GenerateInviteTokenResponse, error) {
|
||||
c, err := pool.GetOCMInviteManagerClient(s.c.OCMInviteManagerEndpoint)
|
||||
if err != nil {
|
||||
return &invitepb.GenerateInviteTokenResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user invite provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GenerateInviteToken(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GenerateInviteToken")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListInviteTokens(ctx context.Context, req *invitepb.ListInviteTokensRequest) (*invitepb.ListInviteTokensResponse, error) {
|
||||
c, err := pool.GetOCMInviteManagerClient(s.c.OCMInviteManagerEndpoint)
|
||||
if err != nil {
|
||||
return &invitepb.ListInviteTokensResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user invite provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListInviteTokens(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListInviteTokens")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ForwardInvite(ctx context.Context, req *invitepb.ForwardInviteRequest) (*invitepb.ForwardInviteResponse, error) {
|
||||
c, err := pool.GetOCMInviteManagerClient(s.c.OCMInviteManagerEndpoint)
|
||||
if err != nil {
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user invite provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ForwardInvite(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ForwardInvite")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) AcceptInvite(ctx context.Context, req *invitepb.AcceptInviteRequest) (*invitepb.AcceptInviteResponse, error) {
|
||||
c, err := pool.GetOCMInviteManagerClient(s.c.OCMInviteManagerEndpoint)
|
||||
if err != nil {
|
||||
return &invitepb.AcceptInviteResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user invite provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.AcceptInvite(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling AcceptInvite")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetAcceptedUser(ctx context.Context, req *invitepb.GetAcceptedUserRequest) (*invitepb.GetAcceptedUserResponse, error) {
|
||||
c, err := pool.GetOCMInviteManagerClient(s.c.OCMInviteManagerEndpoint)
|
||||
if err != nil {
|
||||
return &invitepb.GetAcceptedUserResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user invite provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetAcceptedUser(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetAcceptedUser")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) FindAcceptedUsers(ctx context.Context, req *invitepb.FindAcceptedUsersRequest) (*invitepb.FindAcceptedUsersResponse, error) {
|
||||
c, err := pool.GetOCMInviteManagerClient(s.c.OCMInviteManagerEndpoint)
|
||||
if err != nil {
|
||||
return &invitepb.FindAcceptedUsersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user invite provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.FindAcceptedUsers(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling FindAcceptedUsers")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) DeleteAcceptedUser(ctx context.Context, req *invitepb.DeleteAcceptedUserRequest) (*invitepb.DeleteAcceptedUserResponse, error) {
|
||||
c, err := pool.GetOCMInviteManagerClient(s.c.OCMInviteManagerEndpoint)
|
||||
if err != nil {
|
||||
return &invitepb.DeleteAcceptedUserResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user invite provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.DeleteAcceptedUser(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling FindAcceptedUsers")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
// Copyright 2018-2023 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) IsProviderAllowed(ctx context.Context, req *ocmprovider.IsProviderAllowedRequest) (*ocmprovider.IsProviderAllowedResponse, error) {
|
||||
c, err := pool.GetOCMProviderAuthorizerClient(s.c.OCMProviderAuthorizerEndpoint)
|
||||
if err != nil {
|
||||
return &ocmprovider.IsProviderAllowedResponse{
|
||||
Status: status.NewInternal(ctx, "error getting ocm authorizer provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.IsProviderAllowed(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling IsProviderAllowed")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetInfoByDomain(ctx context.Context, req *ocmprovider.GetInfoByDomainRequest) (*ocmprovider.GetInfoByDomainResponse, error) {
|
||||
c, err := pool.GetOCMProviderAuthorizerClient(s.c.OCMProviderAuthorizerEndpoint)
|
||||
if err != nil {
|
||||
return &ocmprovider.GetInfoByDomainResponse{
|
||||
Status: status.NewInternal(ctx, "error getting ocm authorizer provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetInfoByDomain(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetInfoByDomain")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListAllProviders(ctx context.Context, req *ocmprovider.ListAllProvidersRequest) (*ocmprovider.ListAllProvidersResponse, error) {
|
||||
c, err := pool.GetOCMProviderAuthorizerClient(s.c.OCMProviderAuthorizerEndpoint)
|
||||
if err != nil {
|
||||
return &ocmprovider.ListAllProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting ocm authorizer provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListAllProviders(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListAllProviders")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+465
@@ -0,0 +1,465 @@
|
||||
// Copyright 2018-2023 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
incoming "github.com/cs3org/go-cs3apis/cs3/ocm/incoming/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TODO(labkode): add multi-phase commit logic when commit share or commit ref is enabled.
|
||||
func (s *svc) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest) (*ocm.CreateOCMShareResponse, error) {
|
||||
if len(req.AccessMethods) == 0 {
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "access methods cannot be empty"),
|
||||
}, nil
|
||||
}
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// persist the OCM share in the ocm share provider
|
||||
res, err := c.CreateOCMShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling CreateOCMShare")
|
||||
}
|
||||
|
||||
// add a grant to the storage provider so the share can efficiently be listed
|
||||
// the grant does not grant any permissions. access is granted by the OCM link token
|
||||
// that is used by the public storage provider to impersonate the resource owner
|
||||
status, err := s.addGrant(ctx, req.ResourceId, req.Grantee, req.AccessMethods[0].GetWebdavOptions().Permissions, req.Expiration, nil)
|
||||
switch {
|
||||
case err != nil:
|
||||
appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg(err.Error())
|
||||
return nil, errors.Wrap(err, "gateway: error adding grant to storage")
|
||||
case status.Code == rpc.Code_CODE_UNIMPLEMENTED:
|
||||
appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg("storing grants not supported, ignoring")
|
||||
case status.Code != rpc.Code_CODE_OK:
|
||||
appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg("storing grants is not successful")
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) RemoveOCMShare(ctx context.Context, req *ocm.RemoveOCMShareRequest) (*ocm.RemoveOCMShareResponse, error) {
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
return &ocm.RemoveOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
getShareRes, err := c.GetOCMShare(ctx, &ocm.GetOCMShareRequest{
|
||||
Ref: req.Ref,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetOCMShare")
|
||||
}
|
||||
if getShareRes.Status.Code != rpc.Code_CODE_OK {
|
||||
res := &ocm.RemoveOCMShareResponse{
|
||||
Status: status.NewInternal(ctx,
|
||||
"error getting ocm share when committing to the storage"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
share := getShareRes.Share
|
||||
|
||||
res, err := c.RemoveOCMShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling RemoveOCMShare")
|
||||
}
|
||||
|
||||
// remove the grant from the storage provider
|
||||
status, err := s.removeGrant(ctx, share.GetResourceId(), share.GetGrantee(), share.GetAccessMethods()[0].GetWebdavOptions().GetPermissions(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error removing grant from storage")
|
||||
}
|
||||
if status.Code != rpc.Code_CODE_OK {
|
||||
return &ocm.RemoveOCMShareResponse{
|
||||
Status: status,
|
||||
}, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TODO(labkode): we need to validate share state vs storage grant and storage ref
|
||||
// If there are any inconsistencies, the share needs to be flag as invalid and a background process
|
||||
// or active fix needs to be performed.
|
||||
func (s *svc) GetOCMShare(ctx context.Context, req *ocm.GetOCMShareRequest) (*ocm.GetOCMShareResponse, error) {
|
||||
return s.getOCMShare(ctx, req)
|
||||
}
|
||||
|
||||
func (s *svc) getOCMShare(ctx context.Context, req *ocm.GetOCMShareRequest) (*ocm.GetOCMShareResponse, error) {
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
|
||||
return &ocm.GetOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetOCMShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetOCMShare")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetOCMShareByToken(ctx context.Context, req *ocm.GetOCMShareByTokenRequest) (*ocm.GetOCMShareByTokenResponse, error) {
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetOCMShareProviderClient")
|
||||
}
|
||||
|
||||
res, err := c.GetOCMShareByToken(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetOCMShareByToken")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TODO(labkode): read GetShare comment.
|
||||
func (s *svc) ListOCMShares(ctx context.Context, req *ocm.ListOCMSharesRequest) (*ocm.ListOCMSharesResponse, error) {
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
|
||||
return &ocm.ListOCMSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListOCMShares(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListOCMShares")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) UpdateOCMShare(ctx context.Context, req *ocm.UpdateOCMShareRequest) (*ocm.UpdateOCMShareResponse, error) {
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
|
||||
return &ocm.UpdateOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.UpdateOCMShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling UpdateOCMShare")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListReceivedOCMShares(ctx context.Context, req *ocm.ListReceivedOCMSharesRequest) (*ocm.ListReceivedOCMSharesResponse, error) {
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
|
||||
return &ocm.ListReceivedOCMSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListReceivedOCMShares(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListReceivedOCMShares")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceivedOCMShareRequest) (*ocm.UpdateReceivedOCMShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// retrieve the current received share
|
||||
getShareReq := &ocm.GetReceivedOCMShareRequest{
|
||||
Ref: &ocm.ShareReference{
|
||||
Spec: &ocm.ShareReference_Id{
|
||||
Id: req.Share.Id,
|
||||
},
|
||||
},
|
||||
}
|
||||
getShareRes, err := s.GetReceivedOCMShare(ctx, getShareReq)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("gateway: error calling GetReceivedOCMShare")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
if getShareRes.Status.Code != rpc.Code_CODE_OK {
|
||||
log.Error().Msg("gateway: error calling GetReceivedOCMShare")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
Message: "gateway: error calling GetReceivedOCMShare",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
share := getShareRes.Share
|
||||
if share == nil {
|
||||
log.Error().Err(err).Msg("gateway: got a nil share from GetReceivedOCMShare")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
Message: "gateway: got a nil share from GetReceivedOCMShare",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.UpdateReceivedOCMShare(ctx, req)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
for i := range req.UpdateMask.Paths {
|
||||
switch req.UpdateMask.Paths[i] {
|
||||
case "state":
|
||||
switch req.GetShare().GetState() {
|
||||
case ocm.ShareState_SHARE_STATE_ACCEPTED:
|
||||
// for a transfer this is handled elsewhere
|
||||
case ocm.ShareState_SHARE_STATE_PENDING:
|
||||
// currently no consequences
|
||||
case ocm.ShareState_SHARE_STATE_REJECTED:
|
||||
// TODO
|
||||
return res, nil
|
||||
}
|
||||
case "mount_point":
|
||||
// TODO(labkode): implementing updating mount point
|
||||
err = errtypes.NotSupported("gateway: update of mount point is not yet implemented")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: status.NewUnimplemented(ctx, err, "error updating received share"),
|
||||
}, nil
|
||||
default:
|
||||
return nil, errtypes.NotSupported("updating " + req.UpdateMask.Paths[i] + " is not supported")
|
||||
}
|
||||
}
|
||||
// handle transfer in case it has not already been accepted
|
||||
if s.isTransferShare(share) && req.GetShare().State == ocm.ShareState_SHARE_STATE_ACCEPTED {
|
||||
if share.State == ocm.ShareState_SHARE_STATE_ACCEPTED {
|
||||
log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare, share already accepted.")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_FAILED_PRECONDITION,
|
||||
Message: "Share already accepted.",
|
||||
},
|
||||
}, err
|
||||
}
|
||||
// get provided destination path
|
||||
transferDestinationPath, err := s.getTransferDestinationPath(ctx, req)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
}
|
||||
|
||||
error := s.handleTransfer(ctx, share, transferDestinationPath)
|
||||
if error != nil {
|
||||
log.Err(error).Msg("gateway: error handling transfer in UpdateReceivedOCMShare")
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
},
|
||||
}, error
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) handleTransfer(ctx context.Context, share *ocm.ReceivedShare, transferDestinationPath string) error {
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
protocol, ok := s.getTransferProtocol(share)
|
||||
if !ok {
|
||||
return errors.New("gateway: unable to retrieve transfer protocol")
|
||||
}
|
||||
sourceURI := protocol.Uri
|
||||
|
||||
// get the webdav endpoint of the grantee's idp
|
||||
var granteeIdp string
|
||||
if share.GetGrantee().Type == provider.GranteeType_GRANTEE_TYPE_USER {
|
||||
granteeIdp = share.GetGrantee().GetUserId().Idp
|
||||
}
|
||||
if share.GetGrantee().Type == provider.GranteeType_GRANTEE_TYPE_GROUP {
|
||||
granteeIdp = share.GetGrantee().GetGroupId().Idp
|
||||
}
|
||||
destWebdavEndpoint, err := s.getWebdavEndpoint(ctx, granteeIdp)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare")
|
||||
return err
|
||||
}
|
||||
destWebdavEndpointURL, err := url.Parse(destWebdavEndpoint)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare: unable to parse webdav endpoint \"" + destWebdavEndpoint + "\" into URL structure")
|
||||
return err
|
||||
}
|
||||
destWebdavHost, err := s.getWebdavHost(ctx, granteeIdp)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare")
|
||||
return err
|
||||
}
|
||||
var dstWebdavURLString string
|
||||
if strings.Contains(destWebdavHost, "://") {
|
||||
dstWebdavURLString = destWebdavHost
|
||||
} else {
|
||||
dstWebdavURLString = "http://" + destWebdavHost
|
||||
}
|
||||
dstWebdavHostURL, err := url.Parse(dstWebdavURLString)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare: unable to parse webdav service host \"" + dstWebdavURLString + "\" into URL structure")
|
||||
return err
|
||||
}
|
||||
destServiceHost := dstWebdavHostURL.Host + dstWebdavHostURL.Path
|
||||
// optional prefix must only appear in target url path:
|
||||
// http://...token...@reva.eu/prefix/?name=remote.php/webdav/home/...
|
||||
destEndpointPath := strings.TrimPrefix(destWebdavEndpointURL.Path, dstWebdavHostURL.Path)
|
||||
destEndpointScheme := destWebdavEndpointURL.Scheme
|
||||
destToken := ctxpkg.ContextMustGetToken(ctx)
|
||||
destPath := path.Join(destEndpointPath, transferDestinationPath, path.Base(share.Name))
|
||||
destTargetURI := fmt.Sprintf("%s://%s@%s?name=%s", destEndpointScheme, destToken, destServiceHost, destPath)
|
||||
// var destUri string
|
||||
req := &datatx.CreateTransferRequest{
|
||||
SrcTargetUri: sourceURI,
|
||||
DestTargetUri: destTargetURI,
|
||||
ShareId: share.Id,
|
||||
}
|
||||
|
||||
res, err := s.CreateTransfer(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msgf("gateway: CreateTransfer: %v", res.TxInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *svc) isTransferShare(share *ocm.ReceivedShare) bool {
|
||||
_, ok := s.getTransferProtocol(share)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *svc) getTransferDestinationPath(ctx context.Context, req *ocm.UpdateReceivedOCMShareRequest) (string, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
// the destination path is not part of any protocol, but an opaque field
|
||||
destPathOpaque, ok := req.GetOpaque().GetMap()["transfer_destination_path"]
|
||||
if ok {
|
||||
switch destPathOpaque.Decoder {
|
||||
case "plain":
|
||||
if string(destPathOpaque.Value) != "" {
|
||||
return string(destPathOpaque.Value), nil
|
||||
}
|
||||
default:
|
||||
return "", errtypes.NotSupported("decoder of opaque entry 'transfer_destination_path' not recognized: " + destPathOpaque.Decoder)
|
||||
}
|
||||
}
|
||||
log.Info().Msg("destination path not provided, trying default transfer destination folder")
|
||||
if s.c.DataTransfersFolder == "" {
|
||||
return "", errtypes.NotSupported("no destination path provided and default transfer destination folder is not set")
|
||||
}
|
||||
return s.c.DataTransfersFolder, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetReceivedOCMShare(ctx context.Context, req *ocm.GetReceivedOCMShareRequest) (*ocm.GetReceivedOCMShareResponse, error) {
|
||||
c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
|
||||
return &ocm.GetReceivedOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetReceivedOCMShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetReceivedOCMShare")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) getTransferProtocol(share *ocm.ReceivedShare) (*ocm.WebDAVProtocol, bool) {
|
||||
for _, p := range share.Protocols {
|
||||
if d, ok := p.Term.(*ocm.Protocol_WebdavOptions); ok {
|
||||
if slices.Contains(d.WebdavOptions.AccessTypes, ocm.AccessType_ACCESS_TYPE_DATATX) {
|
||||
return d.WebdavOptions, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (s *svc) CreateOCMIncomingShare(context.Context, *incoming.CreateOCMIncomingShareRequest) (*incoming.CreateOCMIncomingShareResponse, error) {
|
||||
return nil, errtypes.NotSupported("gateway: CreateOCMIncomingShare is not supported")
|
||||
}
|
||||
|
||||
func (s *svc) UpdateOCMIncomingShare(context.Context, *incoming.UpdateOCMIncomingShareRequest) (*incoming.UpdateOCMIncomingShareResponse, error) {
|
||||
return nil, errtypes.NotSupported("gateway: UpdateOCMIncomingShare is not supported")
|
||||
}
|
||||
|
||||
func (s *svc) DeleteOCMIncomingShare(context.Context, *incoming.DeleteOCMIncomingShareRequest) (*incoming.DeleteOCMIncomingShareResponse, error) {
|
||||
return nil, errtypes.NotSupported("gateway: DeleteOCMIncomingShare is not supported")
|
||||
}
|
||||
|
||||
func (s *svc) ListExistingOCMShares(context.Context, *ocm.ListOCMSharesRequest) (*gateway.ListExistingOCMSharesResponse, error) {
|
||||
return nil, errtypes.NotSupported("gateway: ListExistingOCMShares is not supported")
|
||||
}
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
// Copyright 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
)
|
||||
|
||||
func (s *svc) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) {
|
||||
c, err := pool.GetPermissionsClient(s.c.PermissionsEndpoint)
|
||||
if err != nil {
|
||||
return &permissions.CheckPermissionResponse{
|
||||
Status: status.NewInternal(ctx, "error getting permissions client"),
|
||||
}, nil
|
||||
}
|
||||
return c.CheckPermission(ctx, req)
|
||||
}
|
||||
Generated
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) SetKey(ctx context.Context, req *preferences.SetKeyRequest) (*preferences.SetKeyResponse, error) {
|
||||
c, err := pool.GetPreferencesClient(s.c.PreferencesEndpoint)
|
||||
if err != nil {
|
||||
return &preferences.SetKeyResponse{
|
||||
Status: status.NewInternal(ctx, "error getting preferences client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.SetKey(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling SetKey")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetKey(ctx context.Context, req *preferences.GetKeyRequest) (*preferences.GetKeyResponse, error) {
|
||||
c, err := pool.GetPreferencesClient(s.c.PreferencesEndpoint)
|
||||
if err != nil {
|
||||
return &preferences.GetKeyResponse{
|
||||
Status: status.NewInternal(ctx, "error getting preferences client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetKey(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetKey")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) CreatePublicShare(ctx context.Context, req *link.CreatePublicShareRequest) (*link.CreatePublicShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Msg("create public share")
|
||||
|
||||
c, err := pool.GetPublicShareProviderClient(s.c.PublicShareProviderEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.CreatePublicShare(ctx, req)
|
||||
}
|
||||
|
||||
func (s *svc) RemovePublicShare(ctx context.Context, req *link.RemovePublicShareRequest) (*link.RemovePublicShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Msg("remove public share")
|
||||
|
||||
driver, err := pool.GetPublicShareProviderClient(s.c.PublicShareProviderEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.RemovePublicShare(ctx, req)
|
||||
}
|
||||
|
||||
func (s *svc) GetPublicShareByToken(ctx context.Context, req *link.GetPublicShareByTokenRequest) (*link.GetPublicShareByTokenResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Msg("get public share by token")
|
||||
|
||||
driver, err := pool.GetPublicShareProviderClient(s.c.PublicShareProviderEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := driver.GetPublicShareByToken(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetPublicShare(ctx context.Context, req *link.GetPublicShareRequest) (*link.GetPublicShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Msg("get public share")
|
||||
|
||||
pClient, err := pool.GetPublicShareProviderClient(s.c.PublicShareProviderEndpoint)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error connecting to a public share provider")
|
||||
return &link.GetPublicShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return pClient.GetPublicShare(ctx, req)
|
||||
}
|
||||
|
||||
func (s *svc) ListPublicShares(ctx context.Context, req *link.ListPublicSharesRequest) (*link.ListPublicSharesResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Msg("listing public shares")
|
||||
|
||||
pClient, err := pool.GetPublicShareProviderClient(s.c.PublicShareProviderEndpoint)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error connecting to a public share provider")
|
||||
return &link.ListPublicSharesResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := pClient.ListPublicShares(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error listing shares")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListExistingPublicShares(_ context.Context, _ *link.ListPublicSharesRequest) (*gateway.ListExistingPublicSharesResponse, error) {
|
||||
return nil, errtypes.NotSupported("method ListExistingPublicShares not implemented")
|
||||
}
|
||||
|
||||
func (s *svc) UpdatePublicShare(ctx context.Context, req *link.UpdatePublicShareRequest) (*link.UpdatePublicShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Msg("update public share")
|
||||
|
||||
pClient, err := pool.GetPublicShareProviderClient(s.c.PublicShareProviderEndpoint)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error connecting to a public share provider")
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return pClient.UpdatePublicShare(ctx, req)
|
||||
}
|
||||
Generated
Vendored
+1327
File diff suppressed because it is too large
Load Diff
Server/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/gateway/storageprovidercache.go
Generated
Vendored
+272
@@ -0,0 +1,272 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
sdk "github.com/opencloud-eu/reva/v2/pkg/sdk/common"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/cache"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
/*
|
||||
Cached Registry
|
||||
*/
|
||||
|
||||
type cachedRegistryClient struct {
|
||||
c registry.RegistryAPIClient
|
||||
cache cache.ProviderCache
|
||||
}
|
||||
|
||||
func (c *cachedRegistryClient) ListStorageProviders(ctx context.Context, in *registry.ListStorageProvidersRequest, opts ...grpc.CallOption) (*registry.ListStorageProvidersResponse, error) {
|
||||
|
||||
spaceID := sdk.DecodeOpaqueMap(in.Opaque)["space_id"]
|
||||
|
||||
u, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("user not found in context")
|
||||
}
|
||||
|
||||
key := c.cache.GetKey(u.GetId(), spaceID)
|
||||
if key != "" {
|
||||
s := ®istry.ListStorageProvidersResponse{}
|
||||
if err := c.cache.PullFromCache(key, s); err == nil {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.c.ListStorageProviders(ctx, in, opts...)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case resp.Status.Code != rpc.Code_CODE_OK:
|
||||
return resp, nil
|
||||
case spaceID == "":
|
||||
return resp, nil
|
||||
case spaceID == utils.ShareStorageSpaceID: // TODO do we need to compare providerid and spaceid separately?
|
||||
return resp, nil
|
||||
default:
|
||||
return resp, c.cache.PushToCache(key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// not cached
|
||||
|
||||
func (c *cachedRegistryClient) GetStorageProviders(ctx context.Context, in *registry.GetStorageProvidersRequest, opts ...grpc.CallOption) (*registry.GetStorageProvidersResponse, error) {
|
||||
return c.c.GetStorageProviders(ctx, in, opts...)
|
||||
}
|
||||
|
||||
func (c *cachedRegistryClient) GetHome(ctx context.Context, in *registry.GetHomeRequest, opts ...grpc.CallOption) (*registry.GetHomeResponse, error) {
|
||||
return c.c.GetHome(ctx, in, opts...)
|
||||
}
|
||||
|
||||
/*
|
||||
Cached Spaces Provider
|
||||
*/
|
||||
|
||||
type cachedSpacesAPIClient struct {
|
||||
c provider.SpacesAPIClient
|
||||
createPersonalSpaceCache cache.CreatePersonalSpaceCache
|
||||
}
|
||||
|
||||
// CreateStorageSpace creates a storage space
|
||||
func (c *cachedSpacesAPIClient) CreateStorageSpace(ctx context.Context, in *provider.CreateStorageSpaceRequest, opts ...grpc.CallOption) (*provider.CreateStorageSpaceResponse, error) {
|
||||
if in.Type == "personal" {
|
||||
u, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("user not found in context")
|
||||
}
|
||||
|
||||
key := c.createPersonalSpaceCache.GetKey(u.GetId())
|
||||
if key != "" {
|
||||
s := &provider.CreateStorageSpaceResponse{}
|
||||
if err := c.createPersonalSpaceCache.PullFromCache(key, s); err == nil {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
resp, err := c.c.CreateStorageSpace(ctx, in, opts...)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case resp.Status.Code != rpc.Code_CODE_OK && resp.Status.Code != rpc.Code_CODE_ALREADY_EXISTS:
|
||||
return resp, nil
|
||||
case key == "":
|
||||
return resp, nil
|
||||
default:
|
||||
return resp, c.createPersonalSpaceCache.PushToCache(key, resp)
|
||||
}
|
||||
}
|
||||
return c.c.CreateStorageSpace(ctx, in, opts...)
|
||||
}
|
||||
|
||||
func (c *cachedSpacesAPIClient) ListStorageSpaces(ctx context.Context, in *provider.ListStorageSpacesRequest, opts ...grpc.CallOption) (*provider.ListStorageSpacesResponse, error) {
|
||||
return c.c.ListStorageSpaces(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedSpacesAPIClient) UpdateStorageSpace(ctx context.Context, in *provider.UpdateStorageSpaceRequest, opts ...grpc.CallOption) (*provider.UpdateStorageSpaceResponse, error) {
|
||||
return c.c.UpdateStorageSpace(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedSpacesAPIClient) DeleteStorageSpace(ctx context.Context, in *provider.DeleteStorageSpaceRequest, opts ...grpc.CallOption) (*provider.DeleteStorageSpaceResponse, error) {
|
||||
return c.c.DeleteStorageSpace(ctx, in, opts...)
|
||||
}
|
||||
|
||||
/*
|
||||
Cached Storage Provider
|
||||
*/
|
||||
|
||||
type cachedAPIClient struct {
|
||||
c provider.ProviderAPIClient
|
||||
createPersonalSpaceCache cache.CreatePersonalSpaceCache
|
||||
}
|
||||
|
||||
// CreateHome caches calls to CreateHome locally - anyways they only need to be called once per user
|
||||
func (c *cachedAPIClient) CreateHome(ctx context.Context, in *provider.CreateHomeRequest, opts ...grpc.CallOption) (*provider.CreateHomeResponse, error) {
|
||||
u, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("user not found in context")
|
||||
}
|
||||
|
||||
key := c.createPersonalSpaceCache.GetKey(u.GetId())
|
||||
if key != "" {
|
||||
s := &provider.CreateHomeResponse{}
|
||||
if err := c.createPersonalSpaceCache.PullFromCache(key, s); err == nil {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
resp, err := c.c.CreateHome(ctx, in, opts...)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case resp.Status.Code != rpc.Code_CODE_OK && resp.Status.Code != rpc.Code_CODE_ALREADY_EXISTS:
|
||||
return resp, nil
|
||||
case key == "":
|
||||
return resp, nil
|
||||
default:
|
||||
return resp, c.createPersonalSpaceCache.PushToCache(key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// methods below here are not cached, they just call the client directly
|
||||
|
||||
// Stat returns the Resoure info for a given resource
|
||||
func (c *cachedAPIClient) Stat(ctx context.Context, in *provider.StatRequest, opts ...grpc.CallOption) (*provider.StatResponse, error) {
|
||||
return c.c.Stat(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) AddGrant(ctx context.Context, in *provider.AddGrantRequest, opts ...grpc.CallOption) (*provider.AddGrantResponse, error) {
|
||||
return c.c.AddGrant(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) CreateContainer(ctx context.Context, in *provider.CreateContainerRequest, opts ...grpc.CallOption) (*provider.CreateContainerResponse, error) {
|
||||
return c.c.CreateContainer(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) Delete(ctx context.Context, in *provider.DeleteRequest, opts ...grpc.CallOption) (*provider.DeleteResponse, error) {
|
||||
return c.c.Delete(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) DenyGrant(ctx context.Context, in *provider.DenyGrantRequest, opts ...grpc.CallOption) (*provider.DenyGrantResponse, error) {
|
||||
return c.c.DenyGrant(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) GetPath(ctx context.Context, in *provider.GetPathRequest, opts ...grpc.CallOption) (*provider.GetPathResponse, error) {
|
||||
return c.c.GetPath(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) GetQuota(ctx context.Context, in *provider.GetQuotaRequest, opts ...grpc.CallOption) (*provider.GetQuotaResponse, error) {
|
||||
return c.c.GetQuota(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) InitiateFileDownload(ctx context.Context, in *provider.InitiateFileDownloadRequest, opts ...grpc.CallOption) (*provider.InitiateFileDownloadResponse, error) {
|
||||
return c.c.InitiateFileDownload(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) InitiateFileUpload(ctx context.Context, in *provider.InitiateFileUploadRequest, opts ...grpc.CallOption) (*provider.InitiateFileUploadResponse, error) {
|
||||
return c.c.InitiateFileUpload(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) ListGrants(ctx context.Context, in *provider.ListGrantsRequest, opts ...grpc.CallOption) (*provider.ListGrantsResponse, error) {
|
||||
return c.c.ListGrants(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) ListContainerStream(ctx context.Context, in *provider.ListContainerStreamRequest, opts ...grpc.CallOption) (provider.ProviderAPI_ListContainerStreamClient, error) {
|
||||
return c.c.ListContainerStream(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) ListContainer(ctx context.Context, in *provider.ListContainerRequest, opts ...grpc.CallOption) (*provider.ListContainerResponse, error) {
|
||||
return c.c.ListContainer(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) ListFileVersions(ctx context.Context, in *provider.ListFileVersionsRequest, opts ...grpc.CallOption) (*provider.ListFileVersionsResponse, error) {
|
||||
return c.c.ListFileVersions(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) ListRecycleStream(ctx context.Context, in *provider.ListRecycleStreamRequest, opts ...grpc.CallOption) (provider.ProviderAPI_ListRecycleStreamClient, error) {
|
||||
return c.c.ListRecycleStream(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) ListRecycle(ctx context.Context, in *provider.ListRecycleRequest, opts ...grpc.CallOption) (*provider.ListRecycleResponse, error) {
|
||||
return c.c.ListRecycle(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) Move(ctx context.Context, in *provider.MoveRequest, opts ...grpc.CallOption) (*provider.MoveResponse, error) {
|
||||
return c.c.Move(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) RemoveGrant(ctx context.Context, in *provider.RemoveGrantRequest, opts ...grpc.CallOption) (*provider.RemoveGrantResponse, error) {
|
||||
return c.c.RemoveGrant(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) PurgeRecycle(ctx context.Context, in *provider.PurgeRecycleRequest, opts ...grpc.CallOption) (*provider.PurgeRecycleResponse, error) {
|
||||
return c.c.PurgeRecycle(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) RestoreFileVersion(ctx context.Context, in *provider.RestoreFileVersionRequest, opts ...grpc.CallOption) (*provider.RestoreFileVersionResponse, error) {
|
||||
return c.c.RestoreFileVersion(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) RestoreRecycleItem(ctx context.Context, in *provider.RestoreRecycleItemRequest, opts ...grpc.CallOption) (*provider.RestoreRecycleItemResponse, error) {
|
||||
return c.c.RestoreRecycleItem(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) UpdateGrant(ctx context.Context, in *provider.UpdateGrantRequest, opts ...grpc.CallOption) (*provider.UpdateGrantResponse, error) {
|
||||
return c.c.UpdateGrant(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) CreateSymlink(ctx context.Context, in *provider.CreateSymlinkRequest, opts ...grpc.CallOption) (*provider.CreateSymlinkResponse, error) {
|
||||
return c.c.CreateSymlink(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) CreateReference(ctx context.Context, in *provider.CreateReferenceRequest, opts ...grpc.CallOption) (*provider.CreateReferenceResponse, error) {
|
||||
return c.c.CreateReference(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) SetArbitraryMetadata(ctx context.Context, in *provider.SetArbitraryMetadataRequest, opts ...grpc.CallOption) (*provider.SetArbitraryMetadataResponse, error) {
|
||||
return c.c.SetArbitraryMetadata(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) UnsetArbitraryMetadata(ctx context.Context, in *provider.UnsetArbitraryMetadataRequest, opts ...grpc.CallOption) (*provider.UnsetArbitraryMetadataResponse, error) {
|
||||
return c.c.UnsetArbitraryMetadata(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) SetLock(ctx context.Context, in *provider.SetLockRequest, opts ...grpc.CallOption) (*provider.SetLockResponse, error) {
|
||||
return c.c.SetLock(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) GetLock(ctx context.Context, in *provider.GetLockRequest, opts ...grpc.CallOption) (*provider.GetLockResponse, error) {
|
||||
return c.c.GetLock(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) RefreshLock(ctx context.Context, in *provider.RefreshLockRequest, opts ...grpc.CallOption) (*provider.RefreshLockResponse, error) {
|
||||
return c.c.RefreshLock(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) Unlock(ctx context.Context, in *provider.UnlockRequest, opts ...grpc.CallOption) (*provider.UnlockResponse, error) {
|
||||
return c.c.Unlock(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) GetHome(ctx context.Context, in *provider.GetHomeRequest, opts ...grpc.CallOption) (*provider.GetHomeResponse, error) {
|
||||
return c.c.GetHome(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) TouchFile(ctx context.Context, in *provider.TouchFileRequest, opts ...grpc.CallOption) (*provider.TouchFileResponse, error) {
|
||||
return c.c.TouchFile(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) AddLabel(ctx context.Context, in *provider.AddLabelRequest, opts ...grpc.CallOption) (*provider.AddLabelResponse, error) {
|
||||
return c.c.AddLabel(ctx, in, opts...)
|
||||
}
|
||||
func (c *cachedAPIClient) RemoveLabel(ctx context.Context, in *provider.RemoveLabelRequest, opts ...grpc.CallOption) (*provider.RemoveLabelResponse, error) {
|
||||
return c.c.RemoveLabel(ctx, in, opts...)
|
||||
}
|
||||
Generated
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
// Copyright 2018-2021 CERN
|
||||
// Copyright 2026 OpenCloud GmbH
|
||||
//
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
tenant "github.com/cs3org/go-cs3apis/cs3/identity/tenant/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) GetTenant(ctx context.Context, req *tenant.GetTenantRequest) (*tenant.GetTenantResponse, error) {
|
||||
c, err := pool.GetTenantProviderServiceClient(s.c.TenantProviderEndpoint)
|
||||
if err != nil {
|
||||
return &tenant.GetTenantResponse{
|
||||
Status: status.NewInternal(ctx, "error getting tenant service client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetTenant(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetTenant")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetTenantByClaim(ctx context.Context, req *tenant.GetTenantByClaimRequest) (*tenant.GetTenantByClaimResponse, error) {
|
||||
c, err := pool.GetTenantProviderServiceClient(s.c.TenantProviderEndpoint)
|
||||
if err != nil {
|
||||
return &tenant.GetTenantByClaimResponse{
|
||||
Status: status.NewInternal(ctx, "error getting tenant service client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetTenantByClaim(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetTenantByClaim")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *svc) GetUser(ctx context.Context, req *user.GetUserRequest) (*user.GetUserResponse, error) {
|
||||
c, err := pool.GetUserProviderServiceClient(s.c.UserProviderEndpoint)
|
||||
if err != nil {
|
||||
return &user.GetUserResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetUser(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetUser")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetUserByClaim(ctx context.Context, req *user.GetUserByClaimRequest) (*user.GetUserByClaimResponse, error) {
|
||||
c, err := pool.GetUserProviderServiceClient(s.c.UserProviderEndpoint)
|
||||
if err != nil {
|
||||
return &user.GetUserByClaimResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetUserByClaim(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetUserByClaim")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) FindUsers(ctx context.Context, req *user.FindUsersRequest) (*user.FindUsersResponse, error) {
|
||||
c, err := pool.GetUserProviderServiceClient(s.c.UserProviderEndpoint)
|
||||
if err != nil {
|
||||
return &user.FindUsersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.FindUsers(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling FindUsers")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetUserGroups(ctx context.Context, req *user.GetUserGroupsRequest) (*user.GetUserGroupsResponse, error) {
|
||||
c, err := pool.GetUserProviderServiceClient(s.c.UserProviderEndpoint)
|
||||
if err != nil {
|
||||
return &user.GetUserGroupsResponse{
|
||||
Status: status.NewInternal(ctx, "error getting auth client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetUserGroups(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetUserGroups")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+644
@@ -0,0 +1,644 @@
|
||||
// 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/grants"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TODO(labkode): add multi-phase commit logic when commit share or commit ref is enabled.
|
||||
func (s *svc) CreateShare(ctx context.Context, req *collaboration.CreateShareRequest) (*collaboration.CreateShareResponse, error) {
|
||||
return s.addShare(ctx, req)
|
||||
}
|
||||
|
||||
func (s *svc) RemoveShare(ctx context.Context, req *collaboration.RemoveShareRequest) (*collaboration.RemoveShareResponse, error) {
|
||||
return s.removeShare(ctx, req)
|
||||
}
|
||||
|
||||
func (s *svc) UpdateShare(ctx context.Context, req *collaboration.UpdateShareRequest) (*collaboration.UpdateShareResponse, error) {
|
||||
return s.updateShare(ctx, req)
|
||||
}
|
||||
|
||||
// TODO(labkode): we need to validate share state vs storage grant and storage ref
|
||||
// If there are any inconsistencies, the share needs to be flag as invalid and a background process
|
||||
// or active fix needs to be performed.
|
||||
func (s *svc) GetShare(ctx context.Context, req *collaboration.GetShareRequest) (*collaboration.GetShareResponse, error) {
|
||||
return s.getShare(ctx, req)
|
||||
}
|
||||
|
||||
func (s *svc) getShare(ctx context.Context, req *collaboration.GetShareRequest) (*collaboration.GetShareResponse, error) {
|
||||
c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Msg("getShare: failed to get user share provider")
|
||||
return &collaboration.GetShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetShare")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TODO(labkode): read GetShare comment.
|
||||
func (s *svc) ListShares(ctx context.Context, req *collaboration.ListSharesRequest) (*collaboration.ListSharesResponse, error) {
|
||||
c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Msg("ListShares: failed to get user share provider")
|
||||
return &collaboration.ListSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListShares(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListShares")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) ListExistingShares(_ context.Context, _ *collaboration.ListSharesRequest) (*gateway.ListExistingSharesResponse, error) {
|
||||
return nil, errtypes.NotSupported("method ListExistingShares not implemented")
|
||||
}
|
||||
|
||||
func (s *svc) updateShare(ctx context.Context, req *collaboration.UpdateShareRequest) (*collaboration.UpdateShareResponse, error) {
|
||||
// TODO: update wopi server
|
||||
// FIXME This is a workaround that should prevent removing or changing the share permissions when the file is locked.
|
||||
// https://github.com/owncloud/ocis/issues/8474
|
||||
if status, err := s.checkLock(ctx, req.GetShare().GetId()); err != nil {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Msg("UpdateShare: failed to get user share provider")
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share provider client"),
|
||||
}, nil
|
||||
}
|
||||
res, err := c.UpdateShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling UpdateShare")
|
||||
}
|
||||
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if s.c.CommitShareToStorageGrant {
|
||||
creator, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("user not found in context")
|
||||
}
|
||||
|
||||
grant := &provider.Grant{
|
||||
Grantee: res.GetShare().GetGrantee(),
|
||||
Permissions: res.GetShare().GetPermissions().GetPermissions(),
|
||||
Expiration: res.GetShare().GetExpiration(),
|
||||
Creator: creator.GetId(),
|
||||
}
|
||||
var opaque *typesv1beta1.Opaque
|
||||
if refIsSpaceRoot(res.GetShare().GetResourceId()) {
|
||||
opaque = utils.SpaceGrantOpaque()
|
||||
utils.AppendPlainToOpaque(opaque, "spacetype", utils.ReadPlainFromOpaque(req.GetOpaque(), "spacetype"))
|
||||
}
|
||||
updateGrantStatus, err := s.updateGrant(ctx, res.GetShare().GetResourceId(), grant, opaque)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling updateGrant")
|
||||
}
|
||||
|
||||
if updateGrantStatus.Code != rpc.Code_CODE_OK {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: updateGrantStatus,
|
||||
Share: res.GetShare(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TODO(labkode): listing received shares just goes to the user share manager and gets the list of
|
||||
// received shares. The display name of the shares should be the a friendly name, like the basename
|
||||
// of the original file.
|
||||
func (s *svc) ListReceivedShares(ctx context.Context, req *collaboration.ListReceivedSharesRequest) (*collaboration.ListReceivedSharesResponse, error) {
|
||||
logger := appctx.GetLogger(ctx)
|
||||
c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Msg("ListReceivedShares: failed to get user share provider")
|
||||
return &collaboration.ListReceivedSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.ListReceivedShares(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling ListReceivedShares")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) GetReceivedShare(ctx context.Context, req *collaboration.GetReceivedShareRequest) (*collaboration.GetReceivedShareResponse, error) {
|
||||
c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Msg("GetReceivedShare: failed to get user share provider")
|
||||
return &collaboration.GetReceivedShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting received share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.GetReceivedShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetReceivedShare")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// When updating a received share:
|
||||
// if the update contains update for displayName:
|
||||
// 1. if received share is mounted: we also do a rename in the storage
|
||||
// 2. if received share is not mounted: we only rename in user share provider.
|
||||
func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.UpdateReceivedShareRequest) (*collaboration.UpdateReceivedShareResponse, error) {
|
||||
ctx, span := appctx.GetTracerProvider(ctx).Tracer("gateway").Start(ctx, "Gateway.UpdateReceivedShare")
|
||||
defer span.End()
|
||||
|
||||
// sanity checks
|
||||
switch {
|
||||
case req.GetShare() == nil:
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewInvalid(ctx, "updating requires a received share object"),
|
||||
}, nil
|
||||
case req.GetShare().GetShare() == nil:
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewInvalid(ctx, "share missing"),
|
||||
}, nil
|
||||
case req.GetShare().GetShare().GetId() == nil:
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewInvalid(ctx, "share id missing"),
|
||||
}, nil
|
||||
case req.GetShare().GetShare().GetId().GetOpaqueId() == "":
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewInvalid(ctx, "share id empty"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Msg("UpdateReceivedShare: failed to get user share provider")
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return c.UpdateReceivedShare(ctx, req)
|
||||
/*
|
||||
TODO: Leftover from master merge. Do we need this?
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Msg("UpdateReceivedShare: failed to get user share provider")
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share provider client"),
|
||||
}, nil
|
||||
}
|
||||
// check if we have a resource id in the update response that we can use to update references
|
||||
if res.GetShare().GetShare().GetResourceId() == nil {
|
||||
log.Err(err).Msg("gateway: UpdateReceivedShare must return a ResourceId")
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: &rpc.Status{
|
||||
Code: rpc.Code_CODE_INTERNAL,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// properties are updated in the order they appear in the field mask
|
||||
// when an error occurs the request ends and no further fields are updated
|
||||
for i := range req.UpdateMask.Paths {
|
||||
switch req.UpdateMask.Paths[i] {
|
||||
case "state":
|
||||
switch req.GetShare().GetState() {
|
||||
case collaboration.ShareState_SHARE_STATE_ACCEPTED:
|
||||
rpcStatus := s.createReference(ctx, res.GetShare().GetShare().GetResourceId())
|
||||
if rpcStatus.Code != rpc.Code_CODE_OK {
|
||||
return &collaboration.UpdateReceivedShareResponse{Status: rpcStatus}, nil
|
||||
}
|
||||
case collaboration.ShareState_SHARE_STATE_REJECTED:
|
||||
rpcStatus := s.removeReference(ctx, res.GetShare().GetShare().ResourceId)
|
||||
if rpcStatus.Code != rpc.Code_CODE_OK && rpcStatus.Code != rpc.Code_CODE_NOT_FOUND {
|
||||
return &collaboration.UpdateReceivedShareResponse{Status: rpcStatus}, nil
|
||||
}
|
||||
}
|
||||
case "mount_point":
|
||||
// TODO(labkode): implementing updating mount point
|
||||
err = errtypes.NotSupported("gateway: update of mount point is not yet implemented")
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewUnimplemented(ctx, err, "error updating received share"),
|
||||
}, nil
|
||||
default:
|
||||
return nil, errtypes.NotSupported("updating " + req.UpdateMask.Paths[i] + " is not supported")
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
*/
|
||||
}
|
||||
|
||||
func (s *svc) ListExistingReceivedShares(_ context.Context, _ *collaboration.ListReceivedSharesRequest) (*gateway.ListExistingReceivedSharesResponse, error) {
|
||||
return nil, errtypes.NotSupported("Unimplemented")
|
||||
}
|
||||
|
||||
func (s *svc) denyGrant(ctx context.Context, id *provider.ResourceId, g *provider.Grantee, opaque *typesv1beta1.Opaque) (*rpc.Status, error) {
|
||||
ref := &provider.Reference{
|
||||
ResourceId: id,
|
||||
}
|
||||
|
||||
grantReq := &provider.DenyGrantRequest{
|
||||
Ref: ref,
|
||||
Grantee: g,
|
||||
Opaque: opaque,
|
||||
// TODO add creator
|
||||
}
|
||||
|
||||
c, _, err := s.find(ctx, ref)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Interface("reference", ref).
|
||||
Msg("denyGrant: failed to get storage provider")
|
||||
if _, ok := err.(errtypes.IsNotFound); ok {
|
||||
return status.NewNotFound(ctx, "storage provider not found"), nil
|
||||
}
|
||||
return status.NewInternal(ctx, "error finding storage provider"), nil
|
||||
}
|
||||
|
||||
grantRes, err := c.DenyGrant(ctx, grantReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling DenyGrant")
|
||||
}
|
||||
return grantRes.Status, nil
|
||||
}
|
||||
|
||||
func (s *svc) addGrant(ctx context.Context, id *provider.ResourceId, g *provider.Grantee, p *provider.ResourcePermissions, expiration *typesv1beta1.Timestamp, opaque *typesv1beta1.Opaque) (*rpc.Status, error) {
|
||||
ref := &provider.Reference{
|
||||
ResourceId: id,
|
||||
}
|
||||
|
||||
creator, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("user not found in context")
|
||||
}
|
||||
|
||||
grantReq := &provider.AddGrantRequest{
|
||||
Ref: ref,
|
||||
Grant: &provider.Grant{
|
||||
Grantee: g,
|
||||
Permissions: p,
|
||||
Creator: creator.GetId(),
|
||||
Expiration: expiration,
|
||||
},
|
||||
Opaque: opaque,
|
||||
}
|
||||
|
||||
c, _, err := s.find(ctx, ref)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Interface("reference", ref).
|
||||
Msg("addGrant: failed to get storage provider")
|
||||
if _, ok := err.(errtypes.IsNotFound); ok {
|
||||
return status.NewNotFound(ctx, "storage provider not found"), nil
|
||||
}
|
||||
return status.NewInternal(ctx, "error finding storage provider"), nil
|
||||
}
|
||||
|
||||
grantRes, err := c.AddGrant(ctx, grantReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling AddGrant")
|
||||
}
|
||||
return grantRes.Status, nil
|
||||
}
|
||||
|
||||
func (s *svc) updateGrant(ctx context.Context, id *provider.ResourceId, grant *provider.Grant, opaque *typesv1beta1.Opaque) (*rpc.Status, error) {
|
||||
ref := &provider.Reference{
|
||||
ResourceId: id,
|
||||
}
|
||||
|
||||
grantReq := &provider.UpdateGrantRequest{
|
||||
Opaque: opaque,
|
||||
Ref: ref,
|
||||
Grant: grant,
|
||||
}
|
||||
|
||||
c, _, err := s.find(ctx, ref)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Interface("reference", ref).
|
||||
Msg("updateGrant: failed to get storage provider")
|
||||
if _, ok := err.(errtypes.IsNotFound); ok {
|
||||
return status.NewNotFound(ctx, "storage provider not found"), nil
|
||||
}
|
||||
return status.NewInternal(ctx, "error finding storage provider"), nil
|
||||
}
|
||||
|
||||
grantRes, err := c.UpdateGrant(ctx, grantReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling UpdateGrant")
|
||||
}
|
||||
if grantRes.Status.Code != rpc.Code_CODE_OK {
|
||||
return status.NewInternal(ctx,
|
||||
"error committing share to storage grant"), nil
|
||||
}
|
||||
|
||||
return status.NewOK(ctx), nil
|
||||
}
|
||||
|
||||
func (s *svc) removeGrant(ctx context.Context, id *provider.ResourceId, g *provider.Grantee, p *provider.ResourcePermissions, opaque *typesv1beta1.Opaque) (*rpc.Status, error) {
|
||||
ref := &provider.Reference{
|
||||
ResourceId: id,
|
||||
}
|
||||
|
||||
grantReq := &provider.RemoveGrantRequest{
|
||||
Ref: ref,
|
||||
Grant: &provider.Grant{
|
||||
Grantee: g,
|
||||
Permissions: p,
|
||||
},
|
||||
Opaque: opaque,
|
||||
}
|
||||
|
||||
c, _, err := s.find(ctx, ref)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Interface("reference", ref).
|
||||
Msg("removeGrant: failed to get storage provider")
|
||||
if _, ok := err.(errtypes.IsNotFound); ok {
|
||||
return status.NewNotFound(ctx, "storage provider not found"), nil
|
||||
}
|
||||
return status.NewInternal(ctx, "error finding storage provider"), nil
|
||||
}
|
||||
|
||||
grantRes, err := c.RemoveGrant(ctx, grantReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling RemoveGrant")
|
||||
}
|
||||
if grantRes.Status.Code != rpc.Code_CODE_OK {
|
||||
return grantRes.GetStatus(), nil
|
||||
}
|
||||
|
||||
return status.NewOK(ctx), nil
|
||||
}
|
||||
|
||||
func (s *svc) addShare(ctx context.Context, req *collaboration.CreateShareRequest) (*collaboration.CreateShareResponse, error) {
|
||||
c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Msg("CreateShare: failed to get user share provider")
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user share provider client"),
|
||||
}, nil
|
||||
}
|
||||
// TODO the user share manager needs to be able to decide if the current user is allowed to create that share (and not eg. incerase permissions)
|
||||
// jfd: AFAICT this can only be determined by a storage driver - either the storage provider is queried first or the share manager needs to access the storage using a storage driver
|
||||
res, err := c.CreateShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling CreateShare")
|
||||
}
|
||||
if res.Status.Code != rpc.Code_CODE_OK {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
rollBackFn := func(status *rpc.Status) {
|
||||
rmvReq := &collaboration.RemoveShareRequest{
|
||||
Ref: &collaboration.ShareReference{
|
||||
Spec: &collaboration.ShareReference_Key{
|
||||
Key: &collaboration.ShareKey{
|
||||
ResourceId: req.ResourceInfo.Id,
|
||||
Grantee: req.Grant.Grantee,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg("rollback the CreateShare attempt")
|
||||
if resp, err := s.removeShare(ctx, rmvReq); err != nil {
|
||||
appctx.GetLogger(ctx).Debug().Interface("status", resp.GetStatus()).Interface("req", req).Msg(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if s.c.CommitShareToStorageGrant {
|
||||
// If the share is a denial we call denyGrant instead.
|
||||
var status *rpc.Status
|
||||
var opaque *typesv1beta1.Opaque
|
||||
if refIsSpaceRoot(req.ResourceInfo.Id) {
|
||||
opaque = utils.SpaceGrantOpaque()
|
||||
utils.AppendPlainToOpaque(opaque, "spacetype", req.ResourceInfo.GetSpace().GetSpaceType())
|
||||
}
|
||||
if grants.PermissionsEqual(req.Grant.Permissions.Permissions, &provider.ResourcePermissions{}) {
|
||||
status, err = s.denyGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, opaque)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error denying grant in storage")
|
||||
}
|
||||
} else {
|
||||
status, err = s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions, req.Grant.Expiration, opaque)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg(err.Error())
|
||||
rollBackFn(status)
|
||||
return nil, errors.Wrap(err, "gateway: error adding grant to storage")
|
||||
}
|
||||
}
|
||||
|
||||
switch status.Code {
|
||||
case rpc.Code_CODE_OK, rpc.Code_CODE_ALREADY_EXISTS:
|
||||
// ok
|
||||
case rpc.Code_CODE_UNIMPLEMENTED:
|
||||
appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg("storing grants not supported, ignoring")
|
||||
rollBackFn(status)
|
||||
default:
|
||||
appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg("storing grants is not successful")
|
||||
rollBackFn(status)
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status,
|
||||
}, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) removeShare(ctx context.Context, req *collaboration.RemoveShareRequest) (*collaboration.RemoveShareResponse, error) {
|
||||
c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint)
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).
|
||||
Err(err).
|
||||
Msg("RemoveShare: failed to get user share provider")
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user share provider client"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// if we need to commit the share, we need the resource it points to.
|
||||
var share *collaboration.Share
|
||||
// FIXME: I will cause a panic as share will be nil when I'm false
|
||||
if s.c.CommitShareToStorageGrant {
|
||||
getShareReq := &collaboration.GetShareRequest{
|
||||
Ref: req.Ref,
|
||||
}
|
||||
getShareRes, err := c.GetShare(ctx, getShareReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling GetShare")
|
||||
}
|
||||
|
||||
if getShareRes.Status.Code != rpc.Code_CODE_OK {
|
||||
res := &collaboration.RemoveShareResponse{
|
||||
Status: status.NewInternal(ctx,
|
||||
"error getting share when committing to the storage"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
share = getShareRes.Share
|
||||
}
|
||||
|
||||
// TODO: update wopi server
|
||||
// FIXME This is a workaround that should prevent removing or changing the share permissions when the file is locked.
|
||||
// https://github.com/owncloud/ocis/issues/8474
|
||||
if status, err := s.checkShareLock(ctx, share); err != nil {
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
res, err := c.RemoveShare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error calling RemoveShare")
|
||||
}
|
||||
if res.Status.Code != rpc.Code_CODE_OK {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if s.c.CommitShareToStorageGrant {
|
||||
var opaque *typesv1beta1.Opaque
|
||||
if refIsSpaceRoot(share.ResourceId) {
|
||||
opaque = utils.SpaceGrantOpaque()
|
||||
}
|
||||
removeGrantStatus, err := s.removeGrant(ctx, share.ResourceId, share.Grantee, share.Permissions.Permissions, opaque)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error removing grant from storage")
|
||||
}
|
||||
if removeGrantStatus.Code != rpc.Code_CODE_OK {
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: removeGrantStatus,
|
||||
}, err
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *svc) checkLock(ctx context.Context, shareId *collaboration.ShareId) (*rpc.Status, error) {
|
||||
logger := appctx.GetLogger(ctx)
|
||||
getShareRes, err := s.GetShare(ctx, &collaboration.GetShareRequest{
|
||||
Ref: &collaboration.ShareReference{
|
||||
Spec: &collaboration.ShareReference_Id{Id: shareId},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
msg := "gateway: error calling GetShare"
|
||||
logger.Err(err).Interface("share_id", shareId).Msg(msg)
|
||||
return status.NewInternal(ctx, msg), errors.Wrap(err, msg)
|
||||
}
|
||||
if getShareRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
msg := "can not get share stat " + getShareRes.GetStatus().GetMessage()
|
||||
logger.Debug().Interface("share", shareId).Msg(msg)
|
||||
if getShareRes.GetStatus().GetCode() != rpc.Code_CODE_NOT_FOUND {
|
||||
return status.NewNotFound(ctx, msg), errors.New(msg)
|
||||
}
|
||||
return status.NewInternal(ctx, msg), errors.New(msg)
|
||||
}
|
||||
return s.checkShareLock(ctx, getShareRes.Share)
|
||||
}
|
||||
|
||||
func (s *svc) checkShareLock(ctx context.Context, share *collaboration.Share) (*rpc.Status, error) {
|
||||
logger := appctx.GetLogger(ctx)
|
||||
sRes, err := s.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: share.GetResourceId()},
|
||||
ArbitraryMetadataKeys: []string{"lockdiscovery"}})
|
||||
if err != nil {
|
||||
msg := "failed to stat shared resource"
|
||||
logger.Err(err).Interface("resource_id", share.GetResourceId()).Msg(msg)
|
||||
return status.NewInternal(ctx, msg), errors.Wrap(err, msg)
|
||||
}
|
||||
if sRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
msg := "can not get share stat " + sRes.GetStatus().GetMessage()
|
||||
logger.Debug().Interface("lock", sRes.GetInfo().GetLock()).Msg(msg)
|
||||
if sRes.GetStatus().GetCode() != rpc.Code_CODE_NOT_FOUND {
|
||||
return status.NewNotFound(ctx, msg), errors.New(msg)
|
||||
}
|
||||
return status.NewInternal(ctx, msg), errors.New(msg)
|
||||
}
|
||||
|
||||
if sRes.GetInfo().GetLock() != nil {
|
||||
msg := "can not change grants, the shared resource is locked"
|
||||
logger.Debug().Interface("lock", sRes.GetInfo().GetLock()).Msg(msg)
|
||||
return status.NewLocked(ctx, msg), errors.New(msg)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func refIsSpaceRoot(ref *provider.ResourceId) bool {
|
||||
if ref == nil {
|
||||
return false
|
||||
}
|
||||
if ref.SpaceId == "" || ref.OpaqueId == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return ref.SpaceId == ref.OpaqueId
|
||||
}
|
||||
Generated
Vendored
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright 2018-2023 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 gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type webdavEndpoint struct {
|
||||
filePath string
|
||||
endpoint string
|
||||
token string
|
||||
}
|
||||
|
||||
func (s *svc) extractEndpointInfo(ctx context.Context, targetURL string) (*webdavEndpoint, error) {
|
||||
if targetURL == "" {
|
||||
return nil, errtypes.BadRequest("gateway: ref target is an empty uri")
|
||||
}
|
||||
|
||||
uri, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error parsing target uri: "+targetURL)
|
||||
}
|
||||
if uri.Scheme != "webdav" {
|
||||
return nil, errtypes.NotSupported("ref target does not have the webdav scheme")
|
||||
}
|
||||
|
||||
m, err := url.ParseQuery(uri.RawQuery)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gateway: error parsing target resource name")
|
||||
}
|
||||
|
||||
return &webdavEndpoint{
|
||||
filePath: m["name"][0],
|
||||
endpoint: uri.Host,
|
||||
token: uri.User.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *svc) getWebdavEndpoint(ctx context.Context, domain string) (string, error) {
|
||||
meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{
|
||||
Domain: domain,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "gateway: error calling GetInfoByDomain")
|
||||
}
|
||||
for _, s := range meshProvider.ProviderInfo.Services {
|
||||
if strings.ToLower(s.Endpoint.Type.Name) == "webdav" {
|
||||
return s.Endpoint.Path, nil
|
||||
}
|
||||
}
|
||||
return "", errtypes.NotFound(domain)
|
||||
}
|
||||
|
||||
func (s *svc) getWebdavHost(ctx context.Context, domain string) (string, error) {
|
||||
meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{
|
||||
Domain: domain,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "gateway: error calling GetInfoByDomain")
|
||||
}
|
||||
for _, s := range meshProvider.ProviderInfo.Services {
|
||||
if strings.ToLower(s.Endpoint.Type.Name) == "webdav" {
|
||||
return s.Host, nil
|
||||
}
|
||||
}
|
||||
return "", errtypes.NotFound(domain)
|
||||
}
|
||||
|
||||
func appendNameQuery(targetURL string, nameQueries ...string) (string, error) {
|
||||
uri, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
q, err := url.ParseQuery(uri.RawQuery)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := append([]string{q["name"][0]}, nameQueries...)
|
||||
q.Set("name", path.Join(name...))
|
||||
uri.RawQuery = q.Encode()
|
||||
return uri.String(), nil
|
||||
}
|
||||
Generated
Vendored
+207
@@ -0,0 +1,207 @@
|
||||
// Copyright 2018-2020 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 groupprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/group"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/group/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("groupprovider", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getDriver(c *config) (group.Manager, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
|
||||
return nil, errtypes.NotFound(fmt.Sprintf("driver %s not found for group manager", c.Driver))
|
||||
}
|
||||
|
||||
// New returns a new GroupProviderServiceServer.
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupManager, err := getDriver(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := &service{groupmgr: groupManager}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
type service struct {
|
||||
groupmgr group.Manager
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
grouppb.RegisterGroupAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func (s *service) GetGroup(ctx context.Context, req *grouppb.GetGroupRequest) (*grouppb.GetGroupResponse, error) {
|
||||
if req.GroupId == nil {
|
||||
res := &grouppb.GetGroupResponse{
|
||||
Status: status.NewInvalid(ctx, "groupid missing"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
group, err := s.groupmgr.GetGroup(ctx, req.GroupId, req.SkipFetchingMembers)
|
||||
if err != nil {
|
||||
res := &grouppb.GetGroupResponse{}
|
||||
if _, ok := err.(errtypes.NotFound); ok {
|
||||
res.Status = status.NewNotFound(ctx, "group not found")
|
||||
} else {
|
||||
res.Status = status.NewInternal(ctx, "error getting group")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return &grouppb.GetGroupResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Group: group,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetGroupByClaim(ctx context.Context, req *grouppb.GetGroupByClaimRequest) (*grouppb.GetGroupByClaimResponse, error) {
|
||||
group, err := s.groupmgr.GetGroupByClaim(ctx, req.Claim, req.Value, req.SkipFetchingMembers)
|
||||
if err != nil {
|
||||
res := &grouppb.GetGroupByClaimResponse{}
|
||||
if _, ok := err.(errtypes.NotFound); ok {
|
||||
res.Status = status.NewNotFound(ctx, fmt.Sprintf("group not found %s %s", req.Claim, req.Value))
|
||||
} else {
|
||||
res.Status = status.NewInternal(ctx, "error getting group by claim")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return &grouppb.GetGroupByClaimResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Group: group,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) FindGroups(ctx context.Context, req *grouppb.FindGroupsRequest) (*grouppb.FindGroupsResponse, error) {
|
||||
if len(req.Filters) > 1 || req.Filters[0].GetType() != grouppb.Filter_TYPE_QUERY {
|
||||
return nil, fmt.Errorf("only one query filter supported")
|
||||
}
|
||||
|
||||
groups, err := s.groupmgr.FindGroups(ctx, req.Filters[0].GetQuery(), req.SkipFetchingMembers)
|
||||
if err != nil {
|
||||
return &grouppb.FindGroupsResponse{
|
||||
Status: status.NewInternal(ctx, "error finding groups"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sort group by groupname
|
||||
sort.Slice(groups, func(i, j int) bool {
|
||||
return groups[i].GroupName <= groups[j].GroupName
|
||||
})
|
||||
|
||||
return &grouppb.FindGroupsResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Groups: groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetMembers(ctx context.Context, req *grouppb.GetMembersRequest) (*grouppb.GetMembersResponse, error) {
|
||||
if req.GroupId == nil {
|
||||
res := &grouppb.GetMembersResponse{
|
||||
Status: status.NewInvalid(ctx, "groupid missing"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
members, err := s.groupmgr.GetMembers(ctx, req.GroupId)
|
||||
if err != nil {
|
||||
return &grouppb.GetMembersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting group members"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &grouppb.GetMembersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Members: members,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) HasMember(ctx context.Context, req *grouppb.HasMemberRequest) (*grouppb.HasMemberResponse, error) {
|
||||
if req.GroupId == nil || req.UserId == nil {
|
||||
res := &grouppb.HasMemberResponse{
|
||||
Status: status.NewInvalid(ctx, "groupid or userid missing"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
ok, err := s.groupmgr.HasMember(ctx, req.GroupId, req.UserId)
|
||||
if err != nil {
|
||||
return &grouppb.HasMemberResponse{
|
||||
Status: status.NewInternal(ctx, "error checking for group member"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &grouppb.HasMemberResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Ok: ok,
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
// 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 helloworld
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/internal/grpc/services/helloworld/proto"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("helloworld", New)
|
||||
}
|
||||
|
||||
type conf struct {
|
||||
Message string `mapstructure:"message"`
|
||||
}
|
||||
type service struct {
|
||||
conf *conf
|
||||
}
|
||||
|
||||
// New returns a new PreferencesServiceServer
|
||||
// It can be tested like this:
|
||||
// prototool grpc --address 0.0.0.0:9999 --method 'revad.helloworld.HelloWorldService/Hello' --data '{"name": "Alice"}'
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c := &conf{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "helloworld: error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Message == "" {
|
||||
c.Message = "Hello"
|
||||
}
|
||||
service := &service{conf: c}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
proto.RegisterHelloWorldServiceServer(ss, s)
|
||||
}
|
||||
|
||||
func (s *service) Hello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloResponse, error) {
|
||||
if req.Name == "" {
|
||||
req.Name = "Mr. Nobody"
|
||||
}
|
||||
message := fmt.Sprintf("%s %s", s.conf.Message, req.Name)
|
||||
res := &proto.HelloResponse{
|
||||
Message: message,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+224
@@ -0,0 +1,224 @@
|
||||
// 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.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: helloworld.proto
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type HelloRequest struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *HelloRequest) Reset() { *m = HelloRequest{} }
|
||||
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*HelloRequest) ProtoMessage() {}
|
||||
func (*HelloRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_f5f9b8b18f6fb9a6, []int{0}
|
||||
}
|
||||
|
||||
func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_HelloRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *HelloRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_HelloRequest.Merge(m, src)
|
||||
}
|
||||
func (m *HelloRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_HelloRequest.Size(m)
|
||||
}
|
||||
func (m *HelloRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_HelloRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_HelloRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *HelloRequest) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type HelloResponse struct {
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *HelloResponse) Reset() { *m = HelloResponse{} }
|
||||
func (m *HelloResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*HelloResponse) ProtoMessage() {}
|
||||
func (*HelloResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_f5f9b8b18f6fb9a6, []int{1}
|
||||
}
|
||||
|
||||
func (m *HelloResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_HelloResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *HelloResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_HelloResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *HelloResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_HelloResponse.Merge(m, src)
|
||||
}
|
||||
func (m *HelloResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_HelloResponse.Size(m)
|
||||
}
|
||||
func (m *HelloResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_HelloResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_HelloResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *HelloResponse) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*HelloRequest)(nil), "revad.helloworld.HelloRequest")
|
||||
proto.RegisterType((*HelloResponse)(nil), "revad.helloworld.HelloResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_f5f9b8b18f6fb9a6) }
|
||||
|
||||
var fileDescriptor_f5f9b8b18f6fb9a6 = []byte{
|
||||
// 162 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xce, 0x48, 0xcd, 0xc9,
|
||||
0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0x29, 0x2e, 0x4b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
|
||||
0x12, 0x28, 0x4a, 0x2d, 0x4b, 0x4c, 0xd1, 0x43, 0x48, 0x29, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78,
|
||||
0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12,
|
||||
0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, 0x26, 0x17, 0x2f, 0x54, 0x4d, 0x71, 0x41,
|
||||
0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, 0x62, 0x7a, 0xaa, 0x04,
|
||||
0x13, 0x58, 0x1d, 0x8c, 0x6b, 0x14, 0xcb, 0x25, 0x08, 0x56, 0x1a, 0x0e, 0x32, 0x3c, 0x38, 0xb5,
|
||||
0xa8, 0x2c, 0x33, 0x39, 0x55, 0xc8, 0x83, 0x8b, 0x15, 0x2c, 0x28, 0x24, 0xa7, 0x87, 0x6e, 0xbf,
|
||||
0x1e, 0xb2, 0xe5, 0x52, 0xf2, 0x38, 0xe5, 0x21, 0x16, 0x3b, 0xb1, 0x47, 0xb1, 0x82, 0x3d, 0x92,
|
||||
0xc4, 0x06, 0xa6, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe7, 0xab, 0x90, 0x7f, 0xe6, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// HelloWorldServiceClient is the client API for HelloWorldService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type HelloWorldServiceClient interface {
|
||||
Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
|
||||
}
|
||||
|
||||
type helloWorldServiceClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewHelloWorldServiceClient(cc *grpc.ClientConn) HelloWorldServiceClient {
|
||||
return &helloWorldServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *helloWorldServiceClient) Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
|
||||
out := new(HelloResponse)
|
||||
err := c.cc.Invoke(ctx, "/revad.helloworld.HelloWorldService/Hello", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// HelloWorldServiceServer is the server API for HelloWorldService service.
|
||||
type HelloWorldServiceServer interface {
|
||||
Hello(context.Context, *HelloRequest) (*HelloResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedHelloWorldServiceServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedHelloWorldServiceServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedHelloWorldServiceServer) Hello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented")
|
||||
}
|
||||
|
||||
func RegisterHelloWorldServiceServer(s *grpc.Server, srv HelloWorldServiceServer) {
|
||||
s.RegisterService(&_HelloWorldService_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _HelloWorldService_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(HelloRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HelloWorldServiceServer).Hello(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/revad.helloworld.HelloWorldService/Hello",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HelloWorldServiceServer).Hello(ctx, req.(*HelloRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _HelloWorldService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "revad.helloworld.HelloWorldService",
|
||||
HandlerType: (*HelloWorldServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Hello",
|
||||
Handler: _HelloWorldService_Hello_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "helloworld.proto",
|
||||
}
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package revad.helloworld;
|
||||
|
||||
option go_package = "proto";
|
||||
|
||||
service HelloWorldService {
|
||||
rpc Hello(HelloRequest) returns (HelloResponse);
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloResponse {
|
||||
string message = 2;
|
||||
}
|
||||
Server/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/helloworld/proto/prototool.yaml
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
generate:
|
||||
go_options:
|
||||
import_path: github.com/cs3org/reva/cmd/revad/svcs/grpcsvcs/helloworldsvc/proto
|
||||
plugins:
|
||||
- name : go
|
||||
type: go
|
||||
flags: plugins=grpc
|
||||
output: ./
|
||||
Generated
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
// 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 loader
|
||||
|
||||
import (
|
||||
// Load core gRPC services.
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/applicationauth"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/appprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/appregistry"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/authprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/authregistry"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/datatx"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/gateway"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/groupprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/helloworld"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/ocmcore"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/ocminvitemanager"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/ocmproviderauthorizer"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/ocmshareprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/permissions"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/preferences"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/publicshareprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/publicstorageprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/sharesstorageprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/storageprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/storageregistry"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/userprovider"
|
||||
_ "github.com/opencloud-eu/reva/v2/internal/grpc/services/usershareprovider"
|
||||
// Add your own service here
|
||||
)
|
||||
Generated
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
// Copyright 2018-2023 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 ocmcore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
|
||||
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/share"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/share/repository/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils/cfg"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("ocmcore", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf *config
|
||||
repo share.Repository
|
||||
}
|
||||
|
||||
func (c *config) ApplyDefaults() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
ocmcore.RegisterOcmCoreAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func getShareRepository(c *config) (share.Repository, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound(fmt.Sprintf("driver not found: %s", c.Driver))
|
||||
}
|
||||
|
||||
// New creates a new ocm core svc.
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
var c config
|
||||
if err := cfg.Decode(m, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo, err := getShareRepository(&c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &service{
|
||||
conf: &c,
|
||||
repo: repo,
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.ocm.core.v1beta1.OcmCoreAPI/CreateOCMCoreShare"}
|
||||
}
|
||||
|
||||
// CreateOCMCoreShare is called when an OCM request comes into this reva instance from.
|
||||
func (s *service) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCMCoreShareRequest) (*ocmcore.CreateOCMCoreShareResponse, error) {
|
||||
if req.ShareType != ocm.ShareType_SHARE_TYPE_USER {
|
||||
return nil, errtypes.NotSupported("share type not supported")
|
||||
}
|
||||
|
||||
now := &typesv1beta1.Timestamp{
|
||||
Seconds: uint64(time.Now().Unix()),
|
||||
}
|
||||
|
||||
share, err := s.repo.StoreReceivedShare(ctx, &ocm.ReceivedShare{
|
||||
RemoteShareId: req.ResourceId,
|
||||
Name: req.Name,
|
||||
Grantee: &providerpb.Grantee{
|
||||
Type: providerpb.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &providerpb.Grantee_UserId{
|
||||
UserId: req.ShareWith,
|
||||
},
|
||||
},
|
||||
ResourceType: req.ResourceType,
|
||||
ShareType: req.ShareType,
|
||||
Owner: req.Owner,
|
||||
Creator: req.Sender,
|
||||
Protocols: req.Protocols,
|
||||
Ctime: now,
|
||||
Mtime: now,
|
||||
Expiration: req.Expiration,
|
||||
State: ocm.ShareState_SHARE_STATE_PENDING,
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: identify errors
|
||||
return &ocmcore.CreateOCMCoreShareResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ocmcore.CreateOCMCoreShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Id: share.Id.OpaqueId,
|
||||
Created: share.Ctime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) UpdateOCMCoreShare(ctx context.Context, req *ocmcore.UpdateOCMCoreShareRequest) (*ocmcore.UpdateOCMCoreShareResponse, error) {
|
||||
return nil, errtypes.NotSupported("not implemented")
|
||||
}
|
||||
|
||||
func (s *service) DeleteOCMCoreShare(ctx context.Context, req *ocmcore.DeleteOCMCoreShareRequest) (*ocmcore.DeleteOCMCoreShareResponse, error) {
|
||||
return nil, errtypes.NotSupported("not implemented")
|
||||
}
|
||||
Generated
Vendored
+392
@@ -0,0 +1,392 @@
|
||||
// Copyright 2018-2023 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 ocminvitemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
|
||||
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
|
||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/client"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/invite"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/invite/repository/registry"
|
||||
ocmuser "github.com/opencloud-eu/reva/v2/pkg/ocm/user"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils/cfg"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("ocminvitemanager", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
TokenExpiration string `mapstructure:"token_expiration"`
|
||||
OCMClientTimeout int `mapstructure:"ocm_timeout"`
|
||||
OCMClientInsecure bool `mapstructure:"ocm_insecure"`
|
||||
GatewaySVC string `mapstructure:"gatewaysvc" validate:"required"`
|
||||
ProviderDomain string `mapstructure:"provider_domain" validate:"required" docs:"The same domain registered in the provider authorizer"`
|
||||
|
||||
tokenExpiration time.Duration
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf *config
|
||||
repo invite.Repository
|
||||
ocmClient *client.OCMClient
|
||||
gatewaySelector *pool.Selector[gateway.GatewayAPIClient]
|
||||
}
|
||||
|
||||
func (c *config) ApplyDefaults() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
if c.TokenExpiration == "" {
|
||||
c.TokenExpiration = "24h"
|
||||
}
|
||||
|
||||
c.GatewaySVC = sharedconf.GetGatewaySVC(c.GatewaySVC)
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
invitepb.RegisterInviteAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func getInviteRepository(c *config) (invite.Repository, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
// New creates a new OCM invite manager svc.
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
var c config
|
||||
if err := cfg.Decode(m, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := time.ParseDuration(c.TokenExpiration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.tokenExpiration = p
|
||||
|
||||
repo, err := getInviteRepository(&c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gatewaySelector, err := pool.GatewaySelector(c.GatewaySVC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &service{
|
||||
conf: &c,
|
||||
repo: repo,
|
||||
ocmClient: client.New(&client.Config{
|
||||
Timeout: time.Duration(c.OCMClientTimeout) * time.Second,
|
||||
Insecure: c.OCMClientInsecure,
|
||||
}),
|
||||
gatewaySelector: gatewaySelector,
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.ocm.invite.v1beta1.InviteAPI/AcceptInvite", "/cs3.ocm.invite.v1beta1.InviteAPI/GetAcceptedUser"}
|
||||
}
|
||||
|
||||
func (s *service) GenerateInviteToken(ctx context.Context, req *invitepb.GenerateInviteTokenRequest) (*invitepb.GenerateInviteTokenResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
token := CreateToken(s.conf.tokenExpiration, user.GetId(), req.Description)
|
||||
|
||||
if err := s.repo.AddToken(ctx, token); err != nil {
|
||||
return &invitepb.GenerateInviteTokenResponse{
|
||||
Status: status.NewInternal(ctx, "error generating invite token"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &invitepb.GenerateInviteTokenResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
InviteToken: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ListInviteTokens(ctx context.Context, req *invitepb.ListInviteTokensRequest) (*invitepb.ListInviteTokensResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
tokens, err := s.repo.ListTokens(ctx, user.Id)
|
||||
if err != nil {
|
||||
return &invitepb.ListInviteTokensResponse{
|
||||
Status: status.NewInternal(ctx, "error listing tokens"),
|
||||
}, nil
|
||||
}
|
||||
return &invitepb.ListInviteTokensResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
InviteTokens: tokens,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ForwardInvite(ctx context.Context, req *invitepb.ForwardInviteRequest) (*invitepb.ForwardInviteResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
|
||||
ocmEndpoint, err := getOCMEndpoint(req.GetOriginSystemProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Accept the invitation on the remote OCM provider
|
||||
remoteUser, err := s.ocmClient.InviteAccepted(ctx, ocmEndpoint, &client.InviteAcceptedRequest{
|
||||
Token: req.InviteToken.GetToken(),
|
||||
RecipientProvider: s.conf.ProviderDomain,
|
||||
// The UserID is only a string here. To not lose the IDP information we use the LocalUserFederatedID encoding
|
||||
// i.e. UserID@IDP
|
||||
UserID: ocmuser.FormatOCMUser(user.GetId()),
|
||||
Email: user.GetMail(),
|
||||
Name: user.GetDisplayName(),
|
||||
})
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, client.ErrTokenInvalid):
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewInvalid(ctx, "token not valid"),
|
||||
}, nil
|
||||
case errors.Is(err, client.ErrTokenNotFound):
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewNotFound(ctx, "token not found"),
|
||||
}, nil
|
||||
case errors.Is(err, client.ErrUserAlreadyAccepted):
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewAlreadyExists(ctx, err, err.Error()),
|
||||
}, nil
|
||||
case errors.Is(err, client.ErrServiceNotTrusted):
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewPermissionDenied(ctx, err, err.Error()),
|
||||
}, nil
|
||||
default:
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// create a link between the user that accepted the share (in ctx)
|
||||
// and the remote one (the initiator), so at the end of the invitation workflow they
|
||||
// know each other
|
||||
|
||||
// remoteUser.UserID is the federated ID (just a string), to get a unique CS3 userid
|
||||
// we're using the provider domain as the IDP part of the ID
|
||||
remoteUserID := &userpb.UserId{
|
||||
Type: userpb.UserType_USER_TYPE_FEDERATED,
|
||||
Idp: req.GetOriginSystemProvider().Domain,
|
||||
OpaqueId: remoteUser.UserID,
|
||||
}
|
||||
|
||||
// we need to use a unique identifier for federated users
|
||||
remoteUserID = ocmuser.LocalUserFederatedID(remoteUserID, "")
|
||||
|
||||
if err := s.repo.AddRemoteUser(ctx, user.Id, &userpb.User{
|
||||
Id: remoteUserID,
|
||||
Mail: remoteUser.Email,
|
||||
DisplayName: remoteUser.Name,
|
||||
}); err != nil {
|
||||
if errors.Is(err, invite.ErrUserAlreadyAccepted) {
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewAlreadyExists(ctx, err, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &invitepb.ForwardInviteResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
UserId: remoteUserID,
|
||||
Email: remoteUser.Email,
|
||||
DisplayName: remoteUser.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getOCMEndpoint(originProvider *ocmprovider.ProviderInfo) (string, error) {
|
||||
for _, s := range originProvider.Services {
|
||||
if s.Endpoint.Type.Name == "OCM" {
|
||||
return s.Endpoint.Path, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("ocm endpoint not specified for mesh provider")
|
||||
}
|
||||
|
||||
func (s *service) AcceptInvite(ctx context.Context, req *invitepb.AcceptInviteRequest) (*invitepb.AcceptInviteResponse, error) {
|
||||
token, err := s.repo.GetToken(ctx, req.InviteToken.Token)
|
||||
if err != nil {
|
||||
if errors.Is(err, invite.ErrTokenNotFound) {
|
||||
return &invitepb.AcceptInviteResponse{
|
||||
Status: status.NewNotFound(ctx, "token not found"),
|
||||
}, nil
|
||||
}
|
||||
return &invitepb.AcceptInviteResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if !isTokenValid(token) {
|
||||
return &invitepb.AcceptInviteResponse{
|
||||
Status: status.NewInvalid(ctx, "token is not valid"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
initiator, err := s.getUserInfo(ctx, token.UserId)
|
||||
if err != nil {
|
||||
return &invitepb.AcceptInviteResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
remoteUser := req.GetRemoteUser()
|
||||
// we need to use a unique identifier for federated users
|
||||
remoteUser.Id = ocmuser.LocalUserFederatedID(remoteUser.Id, "")
|
||||
|
||||
if err := s.repo.AddRemoteUser(ctx, token.GetUserId(), remoteUser); err != nil {
|
||||
if !errors.Is(err, invite.ErrUserAlreadyAccepted) {
|
||||
// skip error if user was already accepted
|
||||
return &invitepb.AcceptInviteResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &invitepb.AcceptInviteResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
UserId: initiator.GetId(),
|
||||
Email: initiator.Mail,
|
||||
DisplayName: initiator.DisplayName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) getUserInfo(ctx context.Context, id *userpb.UserId) (*userpb.User, error) {
|
||||
gw, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
res, err := gw.GetUser(ctx, &userpb.GetUserRequest{
|
||||
UserId: id,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Status.Code != rpcv1beta1.Code_CODE_OK {
|
||||
return nil, errors.New(res.Status.Message)
|
||||
}
|
||||
|
||||
return res.User, nil
|
||||
}
|
||||
|
||||
func isTokenValid(token *invitepb.InviteToken) bool {
|
||||
return time.Now().Unix() < int64(token.Expiration.Seconds)
|
||||
}
|
||||
|
||||
func (s *service) GetAcceptedUser(ctx context.Context, req *invitepb.GetAcceptedUserRequest) (*invitepb.GetAcceptedUserResponse, error) {
|
||||
user, ok := getUserFilter(ctx, req)
|
||||
if !ok {
|
||||
return &invitepb.GetAcceptedUserResponse{
|
||||
Status: status.NewInvalidArg(ctx, "user not found"),
|
||||
}, nil
|
||||
}
|
||||
remoteUser, err := s.repo.GetRemoteUser(ctx, user.GetId(), req.GetRemoteUserId())
|
||||
if err != nil {
|
||||
return &invitepb.GetAcceptedUserResponse{
|
||||
Status: status.NewInternal(ctx, "error fetching remote user details"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &invitepb.GetAcceptedUserResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
RemoteUser: remoteUser,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getUserFilter(ctx context.Context, req *invitepb.GetAcceptedUserRequest) (*userpb.User, bool) {
|
||||
user, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if ok {
|
||||
return user, true
|
||||
}
|
||||
|
||||
if req.Opaque == nil || req.Opaque.Map == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
v, ok := req.Opaque.Map["user-filter"]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var u userpb.UserId
|
||||
if err := utils.UnmarshalJSONToProtoV1(v.Value, &u); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &userpb.User{Id: &u}, true
|
||||
}
|
||||
|
||||
func (s *service) FindAcceptedUsers(ctx context.Context, req *invitepb.FindAcceptedUsersRequest) (*invitepb.FindAcceptedUsersResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
acceptedUsers, err := s.repo.FindRemoteUsers(ctx, user.GetId(), req.GetFilter())
|
||||
if err != nil {
|
||||
return &invitepb.FindAcceptedUsersResponse{
|
||||
Status: status.NewInternal(ctx, "error finding remote users: "+err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &invitepb.FindAcceptedUsersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
AcceptedUsers: acceptedUsers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) DeleteAcceptedUser(ctx context.Context, req *invitepb.DeleteAcceptedUserRequest) (*invitepb.DeleteAcceptedUserResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
if err := s.repo.DeleteRemoteUser(ctx, user.Id, req.RemoteUserId); err != nil {
|
||||
return &invitepb.DeleteAcceptedUserResponse{
|
||||
Status: status.NewInternal(ctx, "error deleting remote users: "+err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &invitepb.DeleteAcceptedUserResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright 2018-2023 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 ocminvitemanager
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CreateToken creates a InviteToken object for the userID indicated by userID.
|
||||
func CreateToken(expiration time.Duration, userID *userpb.UserId, description string) *invitepb.InviteToken {
|
||||
tokenID := uuid.New().String()
|
||||
now := time.Now()
|
||||
expirationTime := now.Add(expiration)
|
||||
|
||||
return &invitepb.InviteToken{
|
||||
Token: tokenID,
|
||||
UserId: userID,
|
||||
Expiration: &typesv1beta1.Timestamp{
|
||||
Seconds: uint64(expirationTime.Unix()),
|
||||
Nanos: 0,
|
||||
},
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
// 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 ocmproviderauthorizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/provider"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/provider/authorizer/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("ocmproviderauthorizer", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf *config
|
||||
pa provider.Authorizer
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
ocmprovider.RegisterProviderAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func getProviderAuthorizer(c *config) (provider.Authorizer, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// New creates a new OCM provider authorizer svc
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
|
||||
pa, err := getProviderAuthorizer(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &service{
|
||||
conf: c,
|
||||
pa: pa,
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{
|
||||
"/cs3.ocm.provider.v1beta1.ProviderAPI/IsProviderAllowed",
|
||||
"/cs3.ocm.provider.v1beta1.ProviderAPI/ListAllProviders",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) GetInfoByDomain(ctx context.Context, req *ocmprovider.GetInfoByDomainRequest) (*ocmprovider.GetInfoByDomainResponse, error) {
|
||||
domainInfo, err := s.pa.GetInfoByDomain(ctx, req.Domain)
|
||||
if err != nil {
|
||||
return &ocmprovider.GetInfoByDomainResponse{
|
||||
Status: status.NewInternal(ctx, "error getting provider info"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ocmprovider.GetInfoByDomainResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
ProviderInfo: domainInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) IsProviderAllowed(ctx context.Context, req *ocmprovider.IsProviderAllowedRequest) (*ocmprovider.IsProviderAllowedResponse, error) {
|
||||
err := s.pa.IsProviderAllowed(ctx, req.Provider)
|
||||
if err != nil {
|
||||
return &ocmprovider.IsProviderAllowedResponse{
|
||||
Status: status.NewInternal(ctx, "error verifying mesh provider"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ocmprovider.IsProviderAllowedResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ListAllProviders(ctx context.Context, req *ocmprovider.ListAllProvidersRequest) (*ocmprovider.ListAllProvidersResponse, error) {
|
||||
providers, err := s.pa.ListAllProviders(ctx)
|
||||
if err != nil {
|
||||
return &ocmprovider.ListAllProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error retrieving mesh providers"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ocmprovider.ListAllProvidersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Providers: providers,
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+518
@@ -0,0 +1,518 @@
|
||||
// Copyright 2018-2023 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 ocmshareprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/ocmd"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/client"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/share"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/ocm/share/repository/registry"
|
||||
ocmuser "github.com/opencloud-eu/reva/v2/pkg/ocm/user"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/walker"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils/cfg"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("ocmshareprovider", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
ClientTimeout int `mapstructure:"client_timeout"`
|
||||
ClientInsecure bool `mapstructure:"client_insecure"`
|
||||
GatewaySVC string `mapstructure:"gatewaysvc" validate:"required"`
|
||||
ProviderDomain string `mapstructure:"provider_domain" validate:"required" docs:"The same domain registered in the provider authorizer"`
|
||||
WebDAVEndpoint string `mapstructure:"webdav_endpoint" validate:"required"`
|
||||
WebappTemplate string `mapstructure:"webapp_template"`
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf *config
|
||||
repo share.Repository
|
||||
client *client.OCMClient
|
||||
gatewaySelector *pool.Selector[gateway.GatewayAPIClient]
|
||||
webappTmpl *template.Template
|
||||
walker walker.Walker
|
||||
}
|
||||
|
||||
func (c *config) ApplyDefaults() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
if c.ClientTimeout == 0 {
|
||||
c.ClientTimeout = 10
|
||||
}
|
||||
if c.WebappTemplate == "" {
|
||||
c.WebappTemplate = "https://cernbox.cern.ch/external/sciencemesh/{{.Token}}{relative-path-to-shared-resource}"
|
||||
}
|
||||
|
||||
c.GatewaySVC = sharedconf.GetGatewaySVC(c.GatewaySVC)
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
ocm.RegisterOcmAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func getShareRepository(c *config) (share.Repository, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
// New creates a new ocm share provider svc.
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
var c config
|
||||
if err := cfg.Decode(m, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo, err := getShareRepository(&c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := client.New(&client.Config{
|
||||
Timeout: time.Duration(c.ClientTimeout) * time.Second,
|
||||
Insecure: c.ClientInsecure,
|
||||
})
|
||||
|
||||
gatewaySelector, err := pool.GatewaySelector(c.GatewaySVC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tpl, err := template.New("webapp_template").Parse(c.WebappTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
walker := walker.NewWalker(gatewaySelector)
|
||||
|
||||
service := &service{
|
||||
conf: &c,
|
||||
repo: repo,
|
||||
client: client,
|
||||
gatewaySelector: gatewaySelector,
|
||||
webappTmpl: tpl,
|
||||
walker: walker,
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.sharing.ocm.v1beta1.OcmAPI/GetOCMShareByToken"}
|
||||
}
|
||||
|
||||
func getOCMEndpoint(originProvider *ocmprovider.ProviderInfo) (string, error) {
|
||||
for _, s := range originProvider.Services {
|
||||
if s.Endpoint.Type.Name == "OCM" {
|
||||
return s.Endpoint.Path, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("ocm endpoint not specified for mesh provider")
|
||||
}
|
||||
|
||||
func getResourceType(info *providerpb.ResourceInfo) string {
|
||||
switch info.Type {
|
||||
case providerpb.ResourceType_RESOURCE_TYPE_FILE:
|
||||
return "file"
|
||||
case providerpb.ResourceType_RESOURCE_TYPE_CONTAINER:
|
||||
return "folder"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func (s *service) webdavURL(_ context.Context, share *ocm.Share) string {
|
||||
// the url is in the form of https://cernbox.cern.ch/remote.php/dav/ocm/token
|
||||
p, _ := url.JoinPath(s.conf.WebDAVEndpoint, "/dav/ocm", share.GetId().GetOpaqueId())
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *service) getWebdavProtocol(ctx context.Context, share *ocm.Share, m *ocm.AccessMethod_WebdavOptions) *ocmd.WebDAV {
|
||||
var perms []string
|
||||
if m.WebdavOptions.Permissions.InitiateFileDownload {
|
||||
perms = append(perms, "read")
|
||||
}
|
||||
if m.WebdavOptions.Permissions.InitiateFileUpload {
|
||||
perms = append(perms, "write")
|
||||
}
|
||||
|
||||
return &ocmd.WebDAV{
|
||||
Permissions: perms,
|
||||
URI: s.webdavURL(ctx, share),
|
||||
SharedSecret: share.Token,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) getWebappProtocol(share *ocm.Share) *ocmd.Webapp {
|
||||
var b strings.Builder
|
||||
if err := s.webappTmpl.Execute(&b, share); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &ocmd.Webapp{
|
||||
URITemplate: b.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) getProtocols(ctx context.Context, share *ocm.Share) ocmd.Protocols {
|
||||
var p ocmd.Protocols
|
||||
for _, m := range share.AccessMethods {
|
||||
var newProtocol ocmd.Protocol
|
||||
switch t := m.Term.(type) {
|
||||
case *ocm.AccessMethod_WebdavOptions:
|
||||
newProtocol = s.getWebdavProtocol(ctx, share, t)
|
||||
case *ocm.AccessMethod_WebappOptions:
|
||||
newProtocol = s.getWebappProtocol(share)
|
||||
}
|
||||
if newProtocol != nil {
|
||||
p = append(p, newProtocol)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest) (*ocm.CreateOCMShareResponse, error) {
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statRes, err := gatewayClient.Stat(ctx, &providerpb.StatRequest{
|
||||
Ref: &providerpb.Reference{
|
||||
ResourceId: req.ResourceId,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if statRes.Status.Code != rpc.Code_CODE_OK {
|
||||
if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewNotFound(ctx, statRes.Status.Message),
|
||||
}, nil
|
||||
}
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, statRes.Status.Message),
|
||||
}, nil
|
||||
}
|
||||
|
||||
info := statRes.Info
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
tkn := utils.RandString(32)
|
||||
now := time.Now().UnixNano()
|
||||
ts := &typespb.Timestamp{
|
||||
Seconds: uint64(now / 1000000000),
|
||||
Nanos: uint32(now % 1000000000),
|
||||
}
|
||||
|
||||
// 1. persist the share in the repository
|
||||
ocmshare := &ocm.Share{
|
||||
Token: tkn,
|
||||
Name: filepath.Base(info.Path),
|
||||
ResourceId: req.ResourceId,
|
||||
Grantee: req.Grantee,
|
||||
ShareType: ocm.ShareType_SHARE_TYPE_USER,
|
||||
Owner: info.Owner,
|
||||
Creator: user.Id,
|
||||
Ctime: ts,
|
||||
Mtime: ts,
|
||||
Expiration: req.Expiration,
|
||||
AccessMethods: req.AccessMethods,
|
||||
}
|
||||
|
||||
ocmshare, err = s.repo.StoreShare(ctx, ocmshare)
|
||||
if err != nil {
|
||||
if errors.Is(err, share.ErrShareAlreadyExisting) {
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewAlreadyExists(ctx, err, "share already exists"),
|
||||
}, nil
|
||||
}
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 2. create the share on the remote provider
|
||||
// 2.a get the ocm endpoint of the remote provider
|
||||
ocmEndpoint, err := getOCMEndpoint(req.RecipientMeshProvider)
|
||||
if err != nil {
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "the selected provider does not have an OCM endpoint"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 2.b replace outgoing user ids with ocm user ids
|
||||
// unpack the federated user id
|
||||
shareWith := ocmuser.FormatOCMUser(ocmuser.LocalUserFederatedID(req.GetGrantee().GetUserId(), ""))
|
||||
|
||||
// wrap the local user id in a local federated user id
|
||||
owner := ocmuser.FormatOCMUser(ocmuser.LocalUserFederatedID(info.Owner, s.conf.ProviderDomain))
|
||||
sender := ocmuser.FormatOCMUser(ocmuser.LocalUserFederatedID(user.Id, s.conf.ProviderDomain))
|
||||
|
||||
newShareReq := &client.NewShareRequest{
|
||||
ShareWith: shareWith,
|
||||
Name: ocmshare.Name,
|
||||
ProviderID: ocmshare.Id.OpaqueId,
|
||||
Owner: owner,
|
||||
Sender: sender,
|
||||
SenderDisplayName: user.DisplayName,
|
||||
ShareType: "user",
|
||||
ResourceType: getResourceType(info),
|
||||
Protocols: s.getProtocols(ctx, ocmshare),
|
||||
}
|
||||
|
||||
if req.Expiration != nil {
|
||||
newShareReq.Expiration = req.Expiration.Seconds
|
||||
}
|
||||
|
||||
// 2.c make POST /shares request
|
||||
newShareRes, err := s.client.NewShare(ctx, ocmEndpoint, newShareReq)
|
||||
if err != nil {
|
||||
err2 := s.repo.DeleteShare(ctx, user, &ocm.ShareReference{Spec: &ocm.ShareReference_Id{Id: ocmshare.Id}})
|
||||
if err2 != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err2).Str("shareid", ocmshare.GetId().GetOpaqueId()).Msg("could not delete local ocm share")
|
||||
}
|
||||
// TODO remove the share from the local storage
|
||||
switch {
|
||||
case errors.Is(err, client.ErrInvalidParameters):
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, err.Error()),
|
||||
}, nil
|
||||
case errors.Is(err, client.ErrServiceNotTrusted):
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, err.Error()),
|
||||
}, nil
|
||||
default:
|
||||
return &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
res := &ocm.CreateOCMShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: ocmshare,
|
||||
RecipientDisplayName: newShareRes.RecipientDisplayName,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) RemoveOCMShare(ctx context.Context, req *ocm.RemoveOCMShareRequest) (*ocm.RemoveOCMShareResponse, error) {
|
||||
// TODO (gdelmont): notify the remote provider using the /notification ocm endpoint
|
||||
// https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1notifications/post
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
if err := s.repo.DeleteShare(ctx, user, req.Ref); err != nil {
|
||||
if errors.Is(err, share.ErrShareNotFound) {
|
||||
return &ocm.RemoveOCMShareResponse{
|
||||
Status: status.NewNotFound(ctx, "share does not exist"),
|
||||
}, nil
|
||||
}
|
||||
return &ocm.RemoveOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error removing share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ocm.RemoveOCMShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetOCMShare(ctx context.Context, req *ocm.GetOCMShareRequest) (*ocm.GetOCMShareResponse, error) {
|
||||
// if the request is by token, the user does not need to be in the ctx
|
||||
var user *userpb.User
|
||||
if req.Ref.GetToken() == "" {
|
||||
user = ctxpkg.ContextMustGetUser(ctx)
|
||||
}
|
||||
ocmshare, err := s.repo.GetShare(ctx, user, req.Ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, share.ErrShareNotFound) {
|
||||
return &ocm.GetOCMShareResponse{
|
||||
Status: status.NewNotFound(ctx, "share does not exist"),
|
||||
}, nil
|
||||
}
|
||||
return &ocm.GetOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ocm.GetOCMShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: ocmshare,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetOCMShareByToken(ctx context.Context, req *ocm.GetOCMShareByTokenRequest) (*ocm.GetOCMShareByTokenResponse, error) {
|
||||
ocmshare, err := s.repo.GetShare(ctx, nil, &ocm.ShareReference{
|
||||
Spec: &ocm.ShareReference_Token{
|
||||
Token: req.Token,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, share.ErrShareNotFound) {
|
||||
return &ocm.GetOCMShareByTokenResponse{
|
||||
Status: status.NewNotFound(ctx, "share does not exist"),
|
||||
}, nil
|
||||
}
|
||||
return &ocm.GetOCMShareByTokenResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &ocm.GetOCMShareByTokenResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: ocmshare,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ListOCMShares(ctx context.Context, req *ocm.ListOCMSharesRequest) (*ocm.ListOCMSharesResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
shares, err := s.repo.ListShares(ctx, user, req.Filters)
|
||||
if err != nil {
|
||||
return &ocm.ListOCMSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error listing shares"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &ocm.ListOCMSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: shares,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) UpdateOCMShare(ctx context.Context, req *ocm.UpdateOCMShareRequest) (*ocm.UpdateOCMShareResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
if len(req.Field) == 0 {
|
||||
return &ocm.UpdateOCMShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}, nil
|
||||
}
|
||||
_, err := s.repo.UpdateShare(ctx, user, req.Ref, req.Field...)
|
||||
if err != nil {
|
||||
if errors.Is(err, share.ErrShareNotFound) {
|
||||
return &ocm.UpdateOCMShareResponse{
|
||||
Status: status.NewNotFound(ctx, "share does not exist"),
|
||||
}, nil
|
||||
}
|
||||
return &ocm.UpdateOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error updating share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &ocm.UpdateOCMShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) ListReceivedOCMShares(ctx context.Context, req *ocm.ListReceivedOCMSharesRequest) (*ocm.ListReceivedOCMSharesResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
shares, err := s.repo.ListReceivedShares(ctx, user)
|
||||
if err != nil {
|
||||
return &ocm.ListReceivedOCMSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error listing received shares"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &ocm.ListReceivedOCMSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: shares,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceivedOCMShareRequest) (*ocm.UpdateReceivedOCMShareResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
_, err := s.repo.UpdateReceivedShare(ctx, user, req.Share, req.UpdateMask)
|
||||
if err != nil {
|
||||
if errors.Is(err, share.ErrShareNotFound) {
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: status.NewNotFound(ctx, "share does not exist"),
|
||||
}, nil
|
||||
}
|
||||
return &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error updating received share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &ocm.UpdateReceivedOCMShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) GetReceivedOCMShare(ctx context.Context, req *ocm.GetReceivedOCMShareRequest) (*ocm.GetReceivedOCMShareResponse, error) {
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
if user.Id.GetType() == userpb.UserType_USER_TYPE_SERVICE {
|
||||
var uid userpb.UserId
|
||||
_ = utils.ReadJSONFromOpaque(req.Opaque, "userid", &uid)
|
||||
user = &userpb.User{
|
||||
Id: &uid,
|
||||
}
|
||||
}
|
||||
|
||||
ocmshare, err := s.repo.GetReceivedShare(ctx, user, req.Ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, share.ErrShareNotFound) {
|
||||
return &ocm.GetReceivedOCMShareResponse{
|
||||
Status: status.NewNotFound(ctx, "share does not exist"),
|
||||
}, nil
|
||||
}
|
||||
return &ocm.GetReceivedOCMShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting received share: "+err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &ocm.GetReceivedOCMShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: ocmshare,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
Generated
Vendored
+105
@@ -0,0 +1,105 @@
|
||||
// Copyright 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 permissions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/permission"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/permission/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("permissions", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver" docs:"localhome;The permission driver to be used."`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers" docs:"url:pkg/permission/permission.go"`
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type service struct {
|
||||
manager permission.Manager
|
||||
}
|
||||
|
||||
// New returns a new PermissionsServiceServer
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, ok := registry.NewFuncs[c.Driver]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not get permission manager '%s'", c.Driver)
|
||||
}
|
||||
manager, err := f(c.Drivers[c.Driver])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &service{manager: manager}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
permissions.RegisterPermissionsAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func (s *service) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) {
|
||||
var subject string
|
||||
switch ref := req.SubjectRef.Spec.(type) {
|
||||
case *permissions.SubjectReference_UserId:
|
||||
subject = ref.UserId.OpaqueId
|
||||
case *permissions.SubjectReference_GroupId:
|
||||
subject = ref.GroupId.OpaqueId
|
||||
}
|
||||
var status *rpc.Status
|
||||
if ok := s.manager.CheckPermission(req.Permission, subject, req.Ref); ok {
|
||||
status = &rpc.Status{Code: rpc.Code_CODE_OK}
|
||||
} else {
|
||||
status = &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}
|
||||
}
|
||||
return &permissions.CheckPermissionResponse{Status: status}, nil
|
||||
}
|
||||
Generated
Vendored
+134
@@ -0,0 +1,134 @@
|
||||
// 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 preferences
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
preferencespb "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/preferences"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/preferences/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("preferences", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "memory"
|
||||
}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf *config
|
||||
pm preferences.Manager
|
||||
}
|
||||
|
||||
func getPreferencesManager(c *config) (preferences.Manager, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// New returns a new PreferencesServiceServer
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.init()
|
||||
|
||||
pm, err := getPreferencesManager(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &service{
|
||||
conf: c,
|
||||
pm: pm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
preferencespb.RegisterPreferencesAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func (s *service) SetKey(ctx context.Context, req *preferencespb.SetKeyRequest) (*preferencespb.SetKeyResponse, error) {
|
||||
err := s.pm.SetKey(ctx, req.Key.Key, req.Key.Namespace, req.Val)
|
||||
if err != nil {
|
||||
return &preferencespb.SetKeyResponse{
|
||||
Status: status.NewInternal(ctx, "error setting key"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &preferencespb.SetKeyResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetKey(ctx context.Context, req *preferencespb.GetKeyRequest) (*preferencespb.GetKeyResponse, error) {
|
||||
val, err := s.pm.GetKey(ctx, req.Key.Key, req.Key.Namespace)
|
||||
if err != nil {
|
||||
st := status.NewInternal(ctx, "error retrieving key")
|
||||
if _, ok := err.(errtypes.IsNotFound); ok {
|
||||
st = status.NewNotFound(ctx, "key not found")
|
||||
}
|
||||
return &preferencespb.GetKeyResponse{
|
||||
Status: st,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &preferencespb.GetKeyResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Val: val,
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+654
@@ -0,0 +1,654 @@
|
||||
// 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 publicshareprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/password"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/permission"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/grants"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/conversions"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
)
|
||||
|
||||
const getUserCtxErrMsg = "error getting user from context"
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("publicshareprovider", NewDefault)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
GatewayAddr string `mapstructure:"gateway_addr"`
|
||||
AllowedPathsForShares []string `mapstructure:"allowed_paths_for_shares"`
|
||||
EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"`
|
||||
WriteableShareMustHavePassword bool `mapstructure:"writeable_share_must_have_password"`
|
||||
PublicShareMustHavePassword bool `mapstructure:"public_share_must_have_password"`
|
||||
PasswordPolicy map[string]interface{} `mapstructure:"password_policy"`
|
||||
}
|
||||
|
||||
type passwordPolicy struct {
|
||||
MinCharacters int `mapstructure:"min_characters"`
|
||||
MinLowerCaseCharacters int `mapstructure:"min_lowercase_characters"`
|
||||
MinUpperCaseCharacters int `mapstructure:"min_uppercase_characters"`
|
||||
MinDigits int `mapstructure:"min_digits"`
|
||||
MinSpecialCharacters int `mapstructure:"min_special_characters"`
|
||||
BannedPasswordsList map[string]struct{} `mapstructure:"banned_passwords_list"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf *config
|
||||
sm publicshare.Manager
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
allowedPathsForShares []*regexp.Regexp
|
||||
passwordValidator password.Validator
|
||||
}
|
||||
|
||||
func getShareManager(c *config) (publicshare.Manager, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
// TODO(labkode): add ctx to Close.
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.sharing.link.v1beta1.LinkAPI/GetPublicShareByToken"}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
link.RegisterLinkAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding config")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func parsePasswordPolicy(m map[string]interface{}) (*passwordPolicy, error) {
|
||||
p := &passwordPolicy{}
|
||||
if err := mapstructure.Decode(m, p); err != nil {
|
||||
err = errors.Wrap(err, "error decoding password policy config")
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// New creates a new public share provider svc initialized from defaults
|
||||
func NewDefault(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := parsePasswordPolicy(c.PasswordPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.init()
|
||||
|
||||
sm, err := getShareManager(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gatewaySelector, err := pool.GatewaySelector(sharedconf.GetGatewaySVC(c.GatewayAddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(gatewaySelector, sm, c, p)
|
||||
}
|
||||
|
||||
// New creates a new user share provider svc
|
||||
func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sm publicshare.Manager, c *config, p *passwordPolicy) (rgrpc.Service, error) {
|
||||
allowedPathsForShares := make([]*regexp.Regexp, 0, len(c.AllowedPathsForShares))
|
||||
for _, s := range c.AllowedPathsForShares {
|
||||
regex, err := regexp.Compile(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowedPathsForShares = append(allowedPathsForShares, regex)
|
||||
}
|
||||
|
||||
service := &service{
|
||||
conf: c,
|
||||
sm: sm,
|
||||
gatewaySelector: gatewaySelector,
|
||||
allowedPathsForShares: allowedPathsForShares,
|
||||
passwordValidator: newPasswordPolicy(p),
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func newPasswordPolicy(c *passwordPolicy) password.Validator {
|
||||
if c == nil {
|
||||
return password.NewPasswordPolicy(0, 0, 0, 0, 0, nil)
|
||||
}
|
||||
return password.NewPasswordPolicy(
|
||||
c.MinCharacters,
|
||||
c.MinLowerCaseCharacters,
|
||||
c.MinUpperCaseCharacters,
|
||||
c.MinDigits,
|
||||
c.MinSpecialCharacters,
|
||||
c.BannedPasswordsList,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *service) isPathAllowed(path string) bool {
|
||||
if len(s.allowedPathsForShares) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, reg := range s.allowedPathsForShares {
|
||||
if reg.MatchString(path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *service) CreatePublicShare(ctx context.Context, req *link.CreatePublicShareRequest) (*link.CreatePublicShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Str("publicshareprovider", "create").Msg("create public share")
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isInternalLink := grants.PermissionsEqual(req.GetGrant().GetPermissions().GetPermissions(), &provider.ResourcePermissions{})
|
||||
|
||||
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: req.GetResourceInfo().GetId()}})
|
||||
if err != nil {
|
||||
log.Err(err).Interface("resource_id", req.GetResourceInfo().GetId()).Msg("failed to stat resource to share")
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to stat resource to share"),
|
||||
}, err
|
||||
}
|
||||
|
||||
// all users can create internal links
|
||||
if !isInternalLink {
|
||||
// check if the user has the permission in the user role
|
||||
ok, err := utils.CheckPermission(ctx, permission.WritePublicLink, gatewayClient)
|
||||
if err != nil {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed check user permission to write public link"),
|
||||
}, err
|
||||
}
|
||||
if !ok {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to create public links"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// check that user has share permissions
|
||||
if !isInternalLink && !sRes.GetInfo().GetPermissionSet().AddGrant {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "no share permission"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// check if the user can share with the desired permissions. For internal links this is skipped,
|
||||
// users can always create internal links provided they have the AddGrant permission, which was already
|
||||
// checked above
|
||||
if !isInternalLink && !conversions.SufficientCS3Permissions(sRes.GetInfo().GetPermissionSet(), req.GetGrant().GetPermissions().GetPermissions()) {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "insufficient permissions to create that kind of share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validate path
|
||||
if !s.isPathAllowed(req.GetResourceInfo().GetPath()) {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewFailedPrecondition(ctx, nil, "share creation is not allowed for the specified path"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// check that this is a not a personal space root
|
||||
if req.GetResourceInfo().GetId().GetOpaqueId() == req.GetResourceInfo().GetId().GetSpaceId() &&
|
||||
req.GetResourceInfo().GetSpace().GetSpaceType() == "personal" {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "cannot create link on personal space root"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// quick link returns the existing one if already present
|
||||
quickLink, err := checkQuicklink(req.GetResourceInfo())
|
||||
if err != nil {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "invalid quicklink value"),
|
||||
}, nil
|
||||
}
|
||||
if quickLink {
|
||||
f := []*link.ListPublicSharesRequest_Filter{publicshare.ResourceIDFilter(req.GetResourceInfo().GetId())}
|
||||
req := link.ListPublicSharesRequest{Filters: f}
|
||||
res, err := s.ListPublicShares(ctx, &req)
|
||||
if err != nil || res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "could not list public links"),
|
||||
}, nil
|
||||
}
|
||||
for _, l := range res.GetShare() {
|
||||
if l.Quicklink {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: l,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grant := req.GetGrant()
|
||||
|
||||
// validate expiration date
|
||||
if grant.GetExpiration() != nil {
|
||||
expirationDateTime := utils.TSToTime(grant.GetExpiration()).UTC()
|
||||
if expirationDateTime.Before(time.Now().UTC()) {
|
||||
msg := fmt.Sprintf("expiration date is in the past: %s", expirationDateTime.Format(time.RFC3339))
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, msg),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// enforce password if needed
|
||||
setPassword := grant.GetPassword()
|
||||
if !isInternalLink && enforcePassword(false, grant.GetPermissions().GetPermissions(), s.conf) && len(setPassword) == 0 {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "password protection is enforced"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validate password policy
|
||||
if len(setPassword) > 0 {
|
||||
if err := s.passwordValidator.Validate(setPassword); err != nil {
|
||||
return &link.CreatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
res := &link.CreatePublicShareResponse{}
|
||||
share, err := s.sm.CreatePublicShare(ctx, user, req.GetResourceInfo(), req.GetGrant())
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Error().Err(err).Interface("request", req).Msg("could not write public share")
|
||||
res.Status = status.NewInternal(ctx, "error persisting public share:"+err.Error())
|
||||
default:
|
||||
res.Status = status.NewOK(ctx)
|
||||
res.Share = share
|
||||
res.Opaque = utils.AppendPlainToOpaque(nil, "resourcename", sRes.GetInfo().GetName())
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) RemovePublicShare(ctx context.Context, req *link.RemovePublicShareRequest) (*link.RemovePublicShareResponse, error) {
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Str("publicshareprovider", "remove").Msg("remove public share")
|
||||
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
ps, err := s.sm.GetPublicShare(ctx, user, req.GetRef(), false)
|
||||
if err != nil {
|
||||
return &link.RemovePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "error loading public share"),
|
||||
}, err
|
||||
}
|
||||
|
||||
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: ps.ResourceId}})
|
||||
if err != nil {
|
||||
log.Err(err).Interface("resource_id", ps.ResourceId).Msg("failed to stat shared resource")
|
||||
return &link.RemovePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to stat shared resource"),
|
||||
}, err
|
||||
}
|
||||
if !publicshare.IsCreatedByUser(ps, user) {
|
||||
if !sRes.GetInfo().GetPermissionSet().RemoveGrant {
|
||||
return &link.RemovePublicShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to delete public share"),
|
||||
}, err
|
||||
}
|
||||
}
|
||||
err = s.sm.RevokePublicShare(ctx, user, req.Ref)
|
||||
if err != nil {
|
||||
return &link.RemovePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "error deleting public share"),
|
||||
}, err
|
||||
}
|
||||
o := utils.AppendJSONToOpaque(nil, "resourceid", ps.GetResourceId())
|
||||
o = utils.AppendPlainToOpaque(o, "resourcename", sRes.GetInfo().GetName())
|
||||
return &link.RemovePublicShareResponse{
|
||||
Opaque: o,
|
||||
Status: status.NewOK(ctx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetPublicShareByToken(ctx context.Context, req *link.GetPublicShareByTokenRequest) (*link.GetPublicShareByTokenResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Debug().Msg("getting public share by token")
|
||||
|
||||
// there are 2 passes here, and the second request has no password
|
||||
found, err := s.sm.GetPublicShareByToken(ctx, req.GetToken(), req.GetAuthentication(), req.GetSign())
|
||||
switch v := err.(type) {
|
||||
case nil:
|
||||
return &link.GetPublicShareByTokenResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: found,
|
||||
}, nil
|
||||
case errtypes.InvalidCredentials:
|
||||
return &link.GetPublicShareByTokenResponse{
|
||||
Status: status.NewPermissionDenied(ctx, v, "wrong password"),
|
||||
}, nil
|
||||
case errtypes.NotFound:
|
||||
return &link.GetPublicShareByTokenResponse{
|
||||
Status: status.NewNotFound(ctx, "unknown token"),
|
||||
}, nil
|
||||
default:
|
||||
return &link.GetPublicShareByTokenResponse{
|
||||
Status: status.NewInternal(ctx, "unexpected error"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) GetPublicShare(ctx context.Context, req *link.GetPublicShareRequest) (*link.GetPublicShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Str("publicshareprovider", "get").Msg("get public share")
|
||||
|
||||
u, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
log.Error().Msg(getUserCtxErrMsg)
|
||||
}
|
||||
|
||||
ps, err := s.sm.GetPublicShare(ctx, u, req.Ref, req.GetSign())
|
||||
switch {
|
||||
case err != nil:
|
||||
var st *rpc.Status
|
||||
switch err.(type) {
|
||||
case errtypes.IsNotFound:
|
||||
st = status.NewNotFound(ctx, err.Error())
|
||||
default:
|
||||
st = status.NewInternal(ctx, err.Error())
|
||||
}
|
||||
return &link.GetPublicShareResponse{
|
||||
Status: st,
|
||||
}, nil
|
||||
case ps == nil:
|
||||
return &link.GetPublicShareResponse{
|
||||
Status: status.NewNotFound(ctx, "not found"),
|
||||
}, nil
|
||||
default:
|
||||
return &link.GetPublicShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: ps,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) ListPublicShares(ctx context.Context, req *link.ListPublicSharesRequest) (*link.ListPublicSharesResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Str("publicshareprovider", "list").Msg("list public share")
|
||||
user, _ := ctxpkg.ContextGetUser(ctx)
|
||||
|
||||
shares, err := s.sm.ListPublicShares(ctx, user, req.Filters, req.GetSign())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error listing shares")
|
||||
return &link.ListPublicSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error listing public shares"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &link.ListPublicSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: shares,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) UpdatePublicShare(ctx context.Context, req *link.UpdatePublicShareRequest) (*link.UpdatePublicShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
log.Info().Str("publicshareprovider", "update").Msg("update public share")
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
ps, err := s.sm.GetPublicShare(ctx, user, req.GetRef(), false)
|
||||
if err != nil {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "error loading public share"),
|
||||
}, err
|
||||
}
|
||||
|
||||
isInternalLink := isInternalLink(req, ps)
|
||||
|
||||
// check if the user has the permission in the user role
|
||||
if !publicshare.IsCreatedByUser(ps, user) {
|
||||
canWriteLink, err := utils.CheckPermission(ctx, permission.WritePublicLink, gatewayClient)
|
||||
if err != nil {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "error checking permission to write public share"),
|
||||
}, err
|
||||
}
|
||||
if !canWriteLink {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to update public share"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: ps.ResourceId}})
|
||||
if err != nil {
|
||||
log.Err(err).Interface("resource_id", ps.ResourceId).Msg("failed to stat shared resource")
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to stat shared resource"),
|
||||
}, err
|
||||
}
|
||||
if sRes.Status.Code != rpc.Code_CODE_OK {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: sRes.GetStatus(),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
if !isInternalLink && !publicshare.IsCreatedByUser(ps, user) {
|
||||
if !sRes.GetInfo().GetPermissionSet().UpdateGrant {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to update public share"),
|
||||
}, err
|
||||
}
|
||||
}
|
||||
|
||||
// check if the user can change the permissions to the desired permissions
|
||||
updatePermissions := req.GetUpdate().GetType() == link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS
|
||||
if updatePermissions &&
|
||||
!isInternalLink &&
|
||||
!conversions.SufficientCS3Permissions(
|
||||
sRes.GetInfo().GetPermissionSet(),
|
||||
req.GetUpdate().GetGrant().GetPermissions().GetPermissions(),
|
||||
) {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "insufficient permissions to update that kind of share"),
|
||||
}, nil
|
||||
}
|
||||
if updatePermissions {
|
||||
beforePerm, _ := json.Marshal(sRes.GetInfo().GetPermissionSet())
|
||||
afterPerm, _ := json.Marshal(req.GetUpdate().GetGrant().GetPermissions())
|
||||
log.Info().
|
||||
Str("shares", "update").
|
||||
Msgf("updating permissions from %v to: %v",
|
||||
string(beforePerm),
|
||||
string(afterPerm),
|
||||
)
|
||||
}
|
||||
|
||||
grant := req.GetUpdate().GetGrant()
|
||||
|
||||
// validate expiration date
|
||||
if grant.GetExpiration() != nil {
|
||||
expirationDateTime := utils.TSToTime(grant.GetExpiration()).UTC()
|
||||
if expirationDateTime.Before(time.Now().UTC()) {
|
||||
msg := fmt.Sprintf("expiration date is in the past: %s", expirationDateTime.Format(time.RFC3339))
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, msg),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// enforce password if needed
|
||||
var canOptOut bool
|
||||
if !isInternalLink {
|
||||
canOptOut, err = utils.CheckPermission(ctx, permission.DeleteReadOnlyPassword, gatewayClient)
|
||||
if err != nil {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
updatePassword := req.GetUpdate().GetType() == link.UpdatePublicShareRequest_Update_TYPE_PASSWORD
|
||||
setPassword := grant.GetPassword()
|
||||
|
||||
// we update permissions with an empty password and password is not set on the public share
|
||||
emptyPasswordInPermissionUpdate := len(setPassword) == 0 && updatePermissions && !ps.PasswordProtected
|
||||
|
||||
// password is updated, we use the current permissions to check if the user can opt out
|
||||
if updatePassword && !isInternalLink && enforcePassword(canOptOut, ps.GetPermissions().GetPermissions(), s.conf) && len(setPassword) == 0 {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "password protection is enforced"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// permissions are updated, we use the new permissions to check if the user can opt out
|
||||
if emptyPasswordInPermissionUpdate && !isInternalLink && enforcePassword(canOptOut, grant.GetPermissions().GetPermissions(), s.conf) && len(setPassword) == 0 {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "password protection is enforced"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validate password policy
|
||||
if updatePassword && len(setPassword) > 0 {
|
||||
if err := s.passwordValidator.Validate(setPassword); err != nil {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
updateR, err := s.sm.UpdatePublicShare(ctx, user, req)
|
||||
if err != nil {
|
||||
return &link.UpdatePublicShareResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &link.UpdatePublicShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: updateR,
|
||||
Opaque: utils.AppendPlainToOpaque(nil, "resourcename", sRes.GetInfo().GetName()),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func isInternalLink(req *link.UpdatePublicShareRequest, ps *link.PublicShare) bool {
|
||||
switch {
|
||||
case req.GetUpdate().GetType() == link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS:
|
||||
return grants.PermissionsEqual(req.GetUpdate().GetGrant().GetPermissions().GetPermissions(), &provider.ResourcePermissions{})
|
||||
default:
|
||||
return grants.PermissionsEqual(ps.GetPermissions().GetPermissions(), &provider.ResourcePermissions{})
|
||||
}
|
||||
}
|
||||
|
||||
func enforcePassword(canOptOut bool, permissions *provider.ResourcePermissions, conf *config) bool {
|
||||
isReadOnly := conversions.SufficientCS3Permissions(conversions.NewViewerRole().CS3ResourcePermissions(), permissions)
|
||||
if isReadOnly && canOptOut {
|
||||
return false
|
||||
}
|
||||
|
||||
if conf.PublicShareMustHavePassword {
|
||||
return true
|
||||
}
|
||||
|
||||
return !isReadOnly && conf.WriteableShareMustHavePassword
|
||||
}
|
||||
|
||||
func checkQuicklink(info *provider.ResourceInfo) (bool, error) {
|
||||
if info == nil {
|
||||
return false, nil
|
||||
}
|
||||
if m := info.GetArbitraryMetadata().GetMetadata(); m != nil {
|
||||
q, ok := m["quicklink"]
|
||||
// empty string would trigger an error in ParseBool()
|
||||
if !ok || q == "" {
|
||||
return false, nil
|
||||
}
|
||||
quickLink, err := strconv.ParseBool(q)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return quickLink, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
Generated
Vendored
+1006
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+1349
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+1364
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
// 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 storageprovider
|
||||
|
||||
import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
)
|
||||
|
||||
// XS defines an hex-encoded string as checksum.
|
||||
type XS string
|
||||
|
||||
func (x XS) String() string { return string(x) }
|
||||
|
||||
const (
|
||||
// XSInvalid means the checksum type is invalid.
|
||||
XSInvalid XS = "invalid"
|
||||
// XSUnset means the checksum is optional.
|
||||
XSUnset = "unset"
|
||||
// XSAdler32 means the checksum is adler32
|
||||
XSAdler32 = "adler32"
|
||||
// XSMD5 means the checksum is md5
|
||||
XSMD5 = "md5"
|
||||
// XSSHA1 means the checksum is SHA1
|
||||
XSSHA1 = "sha1"
|
||||
// XSSHA256 means the checksum is SHA256.
|
||||
XSSHA256 = "sha256"
|
||||
)
|
||||
|
||||
// GRPC2PKGXS converts the grpc checksum type to an internal pkg type.
|
||||
func GRPC2PKGXS(t provider.ResourceChecksumType) XS {
|
||||
switch t {
|
||||
case provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_INVALID:
|
||||
return XSInvalid
|
||||
case provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_UNSET:
|
||||
return XSUnset
|
||||
case provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_SHA1:
|
||||
return XSSHA1
|
||||
case provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_ADLER32:
|
||||
return XSAdler32
|
||||
case provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_MD5:
|
||||
return XSMD5
|
||||
default:
|
||||
return XSInvalid
|
||||
}
|
||||
}
|
||||
|
||||
// PKG2GRPCXS converts an internal checksum type to the grpc checksum type.
|
||||
func PKG2GRPCXS(xsType string) provider.ResourceChecksumType {
|
||||
switch xsType {
|
||||
case XSUnset:
|
||||
return provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_UNSET
|
||||
case XSAdler32:
|
||||
return provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_ADLER32
|
||||
case XSMD5:
|
||||
return provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_MD5
|
||||
case XSSHA1:
|
||||
return provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_SHA1
|
||||
default:
|
||||
return provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_INVALID
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+177
@@ -0,0 +1,177 @@
|
||||
// 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 storageregistry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
registrypb "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
sdk "github.com/opencloud-eu/reva/v2/pkg/sdk/common"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/registry/registry"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("storageregistry", New)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
reg storage.Registry
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
registrypb.RegisterRegistryAPIServer(ss, s)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "static"
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new StorageBrokerService
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.init()
|
||||
|
||||
reg, err := getRegistry(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := &service{
|
||||
reg: reg,
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getRegistry(c *config) (storage.Registry, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver])
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
func (s *service) ListStorageProviders(ctx context.Context, req *registrypb.ListStorageProvidersRequest) (*registrypb.ListStorageProvidersResponse, error) {
|
||||
pinfos, err := s.reg.ListProviders(ctx, sdk.DecodeOpaqueMap(req.Opaque))
|
||||
if err != nil {
|
||||
return ®istrypb.ListStorageProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error getting list of storage providers"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := ®istrypb.ListStorageProvidersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Providers: pinfos,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) GetStorageProviders(ctx context.Context, req *registrypb.GetStorageProvidersRequest) (*registrypb.GetStorageProvidersResponse, error) {
|
||||
space, err := decodeSpace(req.Opaque)
|
||||
if err != nil {
|
||||
return ®istrypb.GetStorageProvidersResponse{
|
||||
Status: status.NewInvalid(ctx, err.Error()),
|
||||
}, nil
|
||||
}
|
||||
p, err := s.reg.GetProvider(ctx, space)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case errtypes.IsNotFound:
|
||||
return ®istrypb.GetStorageProvidersResponse{
|
||||
Status: status.NewNotFound(ctx, err.Error()),
|
||||
}, nil
|
||||
default:
|
||||
return ®istrypb.GetStorageProvidersResponse{
|
||||
Status: status.NewInternal(ctx, "error finding storage provider"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
res := ®istrypb.GetStorageProvidersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Providers: []*registrypb.ProviderInfo{p},
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func decodeSpace(o *typespb.Opaque) (*provider.StorageSpace, error) {
|
||||
if entry, ok := o.Map["space"]; ok {
|
||||
var unmarshal func([]byte, interface{}) error
|
||||
switch entry.Decoder {
|
||||
case "json":
|
||||
unmarshal = json.Unmarshal
|
||||
case "toml":
|
||||
unmarshal = toml.Unmarshal
|
||||
case "xml":
|
||||
unmarshal = xml.Unmarshal
|
||||
}
|
||||
|
||||
space := &provider.StorageSpace{}
|
||||
return space, unmarshal(entry.Value, space)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("missing space in opaque property")
|
||||
}
|
||||
|
||||
func (s *service) GetHome(ctx context.Context, req *registrypb.GetHomeRequest) (*registrypb.GetHomeResponse, error) {
|
||||
res := ®istrypb.GetHomeResponse{
|
||||
Status: status.NewUnimplemented(ctx, nil, "getHome is no longer used. use List"),
|
||||
}
|
||||
return res, nil
|
||||
|
||||
}
|
||||
Generated
Vendored
+316
@@ -0,0 +1,316 @@
|
||||
// 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 userprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
tenantpb "github.com/cs3org/go-cs3apis/cs3/identity/tenant/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/plugin"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/tenant"
|
||||
tenantRegistry "github.com/opencloud-eu/reva/v2/pkg/tenant/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/user"
|
||||
userRegistry "github.com/opencloud-eu/reva/v2/pkg/user/manager/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("userprovider", New)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
TenantDriver string `mapstructure:"tenant_driver"`
|
||||
TenantDrivers map[string]map[string]interface{} `mapstructure:"tenant_drivers"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
|
||||
// Fall back to user driver/drivers when no tenant-specific config is provided.
|
||||
if c.TenantDriver == "" {
|
||||
c.TenantDriver = c.Driver
|
||||
}
|
||||
if c.TenantDrivers == nil {
|
||||
c.TenantDrivers = c.Drivers
|
||||
}
|
||||
|
||||
// Force "null" driver if multi-tenancy is disabled
|
||||
if !sharedconf.MultiTenantEnabled() {
|
||||
c.TenantDriver = "null"
|
||||
}
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
c.init()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getDriver(c *config) (user.Manager, *plugin.RevaPlugin, error) {
|
||||
p, err := plugin.Load("userprovider", c.Driver)
|
||||
if err == nil {
|
||||
manager, ok := p.Plugin.(user.Manager)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("could not assert the loaded plugin")
|
||||
}
|
||||
pluginConfig := filepath.Base(c.Driver)
|
||||
err = manager.Configure(c.Drivers[pluginConfig])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return manager, p, nil
|
||||
} else if _, ok := err.(errtypes.NotFound); ok {
|
||||
// plugin not found, fetch the driver from the in-memory registry
|
||||
if f, ok := userRegistry.NewFuncs[c.Driver]; ok {
|
||||
mgr, err := f(c.Drivers[c.Driver])
|
||||
return mgr, nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, nil, errtypes.NotFound(fmt.Sprintf("driver %s not found for user manager", c.Driver))
|
||||
}
|
||||
|
||||
func getTenantManager(c *config) (tenant.Manager, error) {
|
||||
if f, ok := tenantRegistry.NewFuncs[c.TenantDriver]; ok {
|
||||
mgr, err := f(c.TenantDrivers[c.TenantDriver])
|
||||
return mgr, err
|
||||
}
|
||||
return nil, errtypes.NotFound(fmt.Sprintf("driver %s not found for tenant manager", c.TenantDriver))
|
||||
}
|
||||
|
||||
// New returns a new UserProviderServiceServer.
|
||||
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userManager, plug, err := getDriver(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenantManager, err := getTenantManager(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewWithManagers(userManager, tenantManager, plug), nil
|
||||
}
|
||||
|
||||
// NewWithManagers returns a new UserProviderService with the given managers.
|
||||
func NewWithManagers(um user.Manager, tm tenant.Manager, plug *plugin.RevaPlugin) rgrpc.Service {
|
||||
return &service{
|
||||
usermgr: um,
|
||||
tenantmgr: tm,
|
||||
plugin: plug,
|
||||
}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
usermgr user.Manager
|
||||
tenantmgr tenant.Manager
|
||||
plugin *plugin.RevaPlugin
|
||||
}
|
||||
|
||||
func (s *service) Close() error {
|
||||
if s.plugin != nil {
|
||||
s.plugin.Kill()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{"/cs3.identity.user.v1beta1.UserAPI/GetUser", "/cs3.identity.user.v1beta1.UserAPI/GetUserByClaim", "/cs3.identity.user.v1beta1.UserAPI/GetUserGroups"}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
userpb.RegisterUserAPIServer(ss, s)
|
||||
tenantpb.RegisterTenantAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func (s *service) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*userpb.GetUserResponse, error) {
|
||||
if req.UserId == nil {
|
||||
res := &userpb.GetUserResponse{
|
||||
Status: status.NewInvalid(ctx, "userid missing"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Only request users from the same tenant as the current user
|
||||
if currentUser, ok := revactx.ContextGetUser(ctx); ok {
|
||||
req.UserId.TenantId = currentUser.GetId().GetTenantId()
|
||||
}
|
||||
|
||||
user, err := s.usermgr.GetUser(ctx, req.UserId, req.SkipFetchingUserGroups)
|
||||
if err != nil {
|
||||
res := &userpb.GetUserResponse{}
|
||||
switch err.(type) {
|
||||
case errtypes.NotFound:
|
||||
res.Status = status.NewNotFound(ctx, "user not found")
|
||||
case errtypes.Unavailable:
|
||||
res.Status = status.NewUnavailable(ctx, "user provider temporarily unavailable")
|
||||
default:
|
||||
res.Status = status.NewInternal(ctx, "error getting user")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res := &userpb.GetUserResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: user,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) GetUserByClaim(ctx context.Context, req *userpb.GetUserByClaimRequest) (*userpb.GetUserByClaimResponse, error) {
|
||||
tenantID := ""
|
||||
if currentUser, ok := revactx.ContextGetUser(ctx); ok {
|
||||
tenantID = currentUser.GetId().GetTenantId()
|
||||
}
|
||||
user, err := s.usermgr.GetUserByClaim(ctx, req.Claim, req.Value, tenantID, req.SkipFetchingUserGroups)
|
||||
if err != nil {
|
||||
res := &userpb.GetUserByClaimResponse{}
|
||||
switch err.(type) {
|
||||
case errtypes.NotFound:
|
||||
res.Status = status.NewNotFound(ctx, fmt.Sprintf("user not found %s %s", req.Claim, req.Value))
|
||||
case errtypes.Unavailable:
|
||||
res.Status = status.NewUnavailable(ctx, "user provider temporarily unavailable")
|
||||
default:
|
||||
res.Status = status.NewInternal(ctx, "error getting user by claim")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res := &userpb.GetUserByClaimResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: user,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) FindUsers(ctx context.Context, req *userpb.FindUsersRequest) (*userpb.FindUsersResponse, error) {
|
||||
if len(req.Filters) > 1 || req.Filters[0].GetType() != userpb.Filter_TYPE_QUERY {
|
||||
return nil, fmt.Errorf("only one query filter supported")
|
||||
}
|
||||
|
||||
currentUser := revactx.ContextMustGetUser(ctx)
|
||||
|
||||
users, err := s.usermgr.FindUsers(ctx, req.Filters[0].GetQuery(), currentUser.GetId().GetTenantId(), req.SkipFetchingUserGroups)
|
||||
if err != nil {
|
||||
res := &userpb.FindUsersResponse{
|
||||
Status: status.NewInternal(ctx, "error finding users"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// sort users by username
|
||||
sort.Slice(users, func(i, j int) bool {
|
||||
return users[i].Username <= users[j].Username
|
||||
})
|
||||
|
||||
res := &userpb.FindUsersResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Users: users,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) GetUserGroups(ctx context.Context, req *userpb.GetUserGroupsRequest) (*userpb.GetUserGroupsResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
if req.UserId == nil {
|
||||
res := &userpb.GetUserGroupsResponse{
|
||||
Status: status.NewInvalid(ctx, "userid missing"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
groups, err := s.usermgr.GetUserGroups(ctx, req.UserId)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Interface("userid", req.UserId).Msg("error getting user groups")
|
||||
res := &userpb.GetUserGroupsResponse{
|
||||
Status: status.NewInternal(ctx, "error getting user groups"),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res := &userpb.GetUserGroupsResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Groups: groups,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) GetTenant(ctx context.Context, req *tenantpb.GetTenantRequest) (*tenantpb.GetTenantResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
t, err := s.tenantmgr.GetTenant(ctx, req.GetTenantId())
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Interface("tenantid", req.GetTenantId()).Msg("error getting tenant")
|
||||
res := &tenantpb.GetTenantResponse{
|
||||
Status: status.NewInternal(ctx, "error getting tenant"),
|
||||
}
|
||||
if _, ok := err.(errtypes.NotFound); ok {
|
||||
res.Status = status.NewNotFound(ctx, "tenant not found")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
return &tenantpb.GetTenantResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Tenant: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetTenantByClaim(ctx context.Context, req *tenantpb.GetTenantByClaimRequest) (*tenantpb.GetTenantByClaimResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
t, err := s.tenantmgr.GetTenantByClaim(ctx, req.GetClaim(), req.GetValue())
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Interface("claim", req.GetClaim()).Interface("value", req.GetValue()).Msg("error getting tenant")
|
||||
res := &tenantpb.GetTenantByClaimResponse{
|
||||
Status: status.NewInternal(ctx, "error getting tenant"),
|
||||
}
|
||||
if _, ok := err.(errtypes.NotFound); ok {
|
||||
res.Status = status.NewNotFound(ctx, "tenant not found")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
return &tenantpb.GetTenantByClaimResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Tenant: t,
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+857
@@ -0,0 +1,857 @@
|
||||
// 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 usershareprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/conversions"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/permission"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/share"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/share/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
_fieldMaskPathMountPoint = "mount_point"
|
||||
_fieldMaskPathPermissions = "permissions"
|
||||
_fieldMaskPathState = "state"
|
||||
_spaceTypePersonal = "personal"
|
||||
_spaceTypeProject = "project"
|
||||
_spaceTypeVirtual = "virtual"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rgrpc.Register("usershareprovider", NewDefault)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
|
||||
GatewayAddr string `mapstructure:"gateway_addr"`
|
||||
AllowedPathsForShares []string `mapstructure:"allowed_paths_for_shares"`
|
||||
}
|
||||
|
||||
func (c *config) init() {
|
||||
if c.Driver == "" {
|
||||
c.Driver = "json"
|
||||
}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
sm share.Manager
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
allowedPathsForShares []*regexp.Regexp
|
||||
}
|
||||
|
||||
func getShareManager(c *config, logger *zerolog.Logger) (share.Manager, error) {
|
||||
if f, ok := registry.NewFuncs[c.Driver]; ok {
|
||||
return f(c.Drivers[c.Driver], logger)
|
||||
}
|
||||
return nil, errtypes.NotFound("driver not found: " + c.Driver)
|
||||
}
|
||||
|
||||
// TODO(labkode): add ctx to Close.
|
||||
func (s *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UnprotectedEndpoints() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *service) Register(ss *grpc.Server) {
|
||||
collaboration.RegisterCollaborationAPIServer(ss, s)
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*config, error) {
|
||||
c := &config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
err = errors.Wrap(err, "error decoding conf")
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// New creates a new user share provider svc initialized from defaults
|
||||
func NewDefault(m map[string]any, ss *grpc.Server, logger *zerolog.Logger) (rgrpc.Service, error) {
|
||||
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.init()
|
||||
|
||||
sm, err := getShareManager(c, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allowedPathsForShares := make([]*regexp.Regexp, 0, len(c.AllowedPathsForShares))
|
||||
for _, s := range c.AllowedPathsForShares {
|
||||
regex, err := regexp.Compile(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowedPathsForShares = append(allowedPathsForShares, regex)
|
||||
}
|
||||
|
||||
gatewaySelector, err := pool.GatewaySelector(sharedconf.GetGatewaySVC(c.GatewayAddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return New(gatewaySelector, sm, allowedPathsForShares), nil
|
||||
}
|
||||
|
||||
// New creates a new user share provider svc
|
||||
func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sm share.Manager, allowedPathsForShares []*regexp.Regexp) rgrpc.Service {
|
||||
service := &service{
|
||||
sm: sm,
|
||||
gatewaySelector: gatewaySelector,
|
||||
allowedPathsForShares: allowedPathsForShares,
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func (s *service) isPathAllowed(path string) bool {
|
||||
if len(s.allowedPathsForShares) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, reg := range s.allowedPathsForShares {
|
||||
if reg.MatchString(path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShareRequest) (*collaboration.CreateShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
// check if the grantee is a user or group
|
||||
if req.GetGrant().GetGrantee().GetType() == provider.GranteeType_GRANTEE_TYPE_USER {
|
||||
// check if the tenantId of the user matches the tenantId of the target user
|
||||
if user.GetId().GetTenantId() != req.GetGrant().GetGrantee().GetUserId().GetTenantId() {
|
||||
log.Warn().Msg("user tenantId does not match the target user tenantId, this is not supported yet")
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "user tenantId does not match the target user tenantId"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if the user has the permission to create shares at all
|
||||
ok, err := utils.CheckPermission(ctx, permission.WriteShare, gatewayClient)
|
||||
if err != nil {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed check user permission to write public link"),
|
||||
}, err
|
||||
}
|
||||
if !ok {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to create public links"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// use logged in user Idp as default, if the Grantee does not have an IDP set.
|
||||
if req.GetGrant().GetGrantee().GetType() == provider.GranteeType_GRANTEE_TYPE_USER && req.GetGrant().GetGrantee().GetUserId().GetIdp() == "" {
|
||||
req.GetGrant().GetGrantee().Id = &provider.Grantee_UserId{
|
||||
UserId: &userpb.UserId{
|
||||
OpaqueId: req.GetGrant().GetGrantee().GetUserId().GetOpaqueId(),
|
||||
Idp: user.GetId().GetIdp(),
|
||||
Type: userpb.UserType_USER_TYPE_PRIMARY},
|
||||
}
|
||||
}
|
||||
// some for group grantees
|
||||
if req.GetGrant().GetGrantee().GetType() == provider.GranteeType_GRANTEE_TYPE_GROUP && req.GetGrant().GetGrantee().GetGroupId().GetIdp() == "" {
|
||||
// use logged in user Idp as default.
|
||||
req.GetGrant().GetGrantee().Id = &provider.Grantee_GroupId{
|
||||
GroupId: &grouppb.GroupId{
|
||||
OpaqueId: req.GetGrant().GetGrantee().GetGroupId().GetOpaqueId(),
|
||||
Idp: user.GetId().GetIdp(),
|
||||
Type: grouppb.GroupType_GROUP_TYPE_REGULAR},
|
||||
}
|
||||
}
|
||||
|
||||
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: req.GetResourceInfo().GetId()}})
|
||||
if err != nil {
|
||||
log.Err(err).Interface("resource_id", req.GetResourceInfo().GetId()).Msg("failed to stat resource to share")
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to stat shared resource"),
|
||||
}, err
|
||||
}
|
||||
// the user needs to have the AddGrant permissions on the Resource to be able to create a share
|
||||
if !sRes.GetInfo().GetPermissionSet().AddGrant {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to add grants on shared resource"),
|
||||
}, err
|
||||
}
|
||||
|
||||
isSpaceRoot := utils.IsSpaceRoot(sRes.GetInfo())
|
||||
var noEvent bool
|
||||
|
||||
if isSpaceRoot {
|
||||
if sRes.GetInfo().GetSpace().GetSpaceType() == _spaceTypePersonal || sRes.GetInfo().GetSpace().GetSpaceType() == _spaceTypeVirtual {
|
||||
return &collaboration.CreateShareResponse{Status: status.NewInvalid(ctx, "space type is not eligible for sharing")}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow share to myself or the owner if share is for a user
|
||||
if req.GetGrant().GetGrantee().GetType() == provider.GranteeType_GRANTEE_TYPE_USER &&
|
||||
(utils.UserEqual(req.GetGrant().GetGrantee().GetUserId(), user.Id) || utils.UserEqual(req.GetGrant().GetGrantee().GetUserId(), sRes.GetInfo().GetOwner())) {
|
||||
|
||||
denySelfShare := true
|
||||
// To allow adding the initial "mananger" share for the creator of a space when need to make an exception here
|
||||
// if the shared resource is a space root, that does not have any Share existin yet. Note, that we have already
|
||||
// verified that the user adding the share does really have "AddGrant" permissions on the affected resource, so
|
||||
// this "hack" should be ok.
|
||||
if isSpaceRoot {
|
||||
shares, err := s.sm.ListShares(
|
||||
ctx,
|
||||
[]*collaboration.Filter{share.ResourceIDFilter(req.GetResourceInfo().GetId())},
|
||||
)
|
||||
if err != nil {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to list existing shares"),
|
||||
}, nil
|
||||
}
|
||||
if len(shares) == 0 {
|
||||
denySelfShare = false
|
||||
noEvent = true
|
||||
}
|
||||
}
|
||||
if denySelfShare {
|
||||
err := errtypes.BadRequest("jsoncs3: owner/creator and grantee are the same")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// resharing is forbidden for not space roots
|
||||
if !isSpaceRoot {
|
||||
// Resharing of Files/Directories is forbidden. So the grants must not allow the "grant" permissions
|
||||
if HasGrantPermissions(req.GetGrant().GetPermissions().GetPermissions()) {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "resharing not supported"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
// check if the share creator has sufficient permissions to do so.
|
||||
if shareCreationAllowed := conversions.SufficientCS3Permissions(
|
||||
sRes.GetInfo().GetPermissionSet(),
|
||||
req.GetGrant().GetPermissions().GetPermissions(),
|
||||
); !shareCreationAllowed {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "insufficient permissions to create that kind of share"),
|
||||
}, nil
|
||||
}
|
||||
// check if the requested permission are plausible for the Resource
|
||||
if sRes.GetInfo().GetType() == provider.ResourceType_RESOURCE_TYPE_FILE {
|
||||
if newPermissions := req.GetGrant().GetPermissions().GetPermissions(); newPermissions.GetCreateContainer() || newPermissions.GetMove() || newPermissions.GetDelete() {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewInvalid(ctx, "cannot set the requested permissions on that type of resource"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if !s.isPathAllowed(req.GetResourceInfo().GetPath()) {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewFailedPrecondition(ctx, nil, "share creation is not allowed for the specified path"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
createdShare, err := s.sm.Share(ctx, req.GetResourceInfo(), req.GetGrant())
|
||||
if err != nil {
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewStatusFromErrType(ctx, "error creating share", err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var opaque *typesv1beta1.Opaque
|
||||
if isSpaceRoot {
|
||||
opaque = utils.SpaceGrantOpaque()
|
||||
}
|
||||
|
||||
if noEvent {
|
||||
opaque = &typesv1beta1.Opaque{
|
||||
Map: map[string]*typesv1beta1.OpaqueEntry{
|
||||
"noevent": {},
|
||||
},
|
||||
}
|
||||
}
|
||||
return &collaboration.CreateShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: createdShare,
|
||||
Opaque: utils.AppendPlainToOpaque(opaque, "resourcename", sRes.GetInfo().GetName()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func HasGrantPermissions(p *provider.ResourcePermissions) bool {
|
||||
return p.GetAddGrant() || p.GetUpdateGrant() || p.GetRemoveGrant() || p.GetDenyGrant()
|
||||
}
|
||||
|
||||
// spaceRootHasRemainingManager returns true if the given resource has at least one share
|
||||
// (excluding the share with excludeShareOpaqueID) that grants both AddGrant and RemoveGrant.
|
||||
func (s *service) spaceRootHasRemainingManager(ctx context.Context, resourceID *provider.ResourceId, excludeShareOpaqueID string) (bool, error) {
|
||||
shares, err := s.sm.ListShares(ctx, []*collaboration.Filter{
|
||||
{
|
||||
Type: collaboration.Filter_TYPE_RESOURCE_ID,
|
||||
Term: &collaboration.Filter_ResourceId{ResourceId: resourceID},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, s := range shares {
|
||||
if s.GetId().GetOpaqueId() == excludeShareOpaqueID {
|
||||
continue
|
||||
}
|
||||
perms := s.GetPermissions().GetPermissions()
|
||||
if perms.GetAddGrant() && perms.GetRemoveGrant() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *service) RemoveShare(ctx context.Context, req *collaboration.RemoveShareRequest) (*collaboration.RemoveShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
share, err := s.sm.GetShare(ctx, req.Ref)
|
||||
if err != nil {
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: share.GetResourceId()}})
|
||||
if err != nil {
|
||||
log.Err(err).Interface("resource_id", share.GetResourceId()).Msg("failed to stat shared resource")
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to stat shared resource"),
|
||||
}, err
|
||||
}
|
||||
// the requesting user needs to be either the Owner/Creator of the share or have the RemoveGrant permissions on the Resource
|
||||
switch {
|
||||
case utils.UserEqual(user.GetId(), share.GetCreator()) || utils.UserEqual(user.GetId(), share.GetOwner()):
|
||||
fallthrough
|
||||
case sRes.GetInfo().GetPermissionSet().RemoveGrant:
|
||||
break
|
||||
default:
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to remove grants on shared resource"),
|
||||
}, err
|
||||
}
|
||||
|
||||
// For space root shares, ensure at least one share with both AddGrant and RemoveGrant will remain
|
||||
// so the space always has a manager who can administer grants.
|
||||
if utils.IsSpaceRoot(sRes.GetInfo()) {
|
||||
if ok, err := s.spaceRootHasRemainingManager(ctx, share.GetResourceId(), share.GetId().GetOpaqueId()); err != nil {
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to list shares for space root"),
|
||||
}, nil
|
||||
} else if !ok {
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "cannot remove the last share with manager permissions on a space root"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
err = s.sm.Unshare(ctx, req.Ref)
|
||||
if err != nil {
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Status: status.NewInternal(ctx, "error removing share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var opaque *typesv1beta1.Opaque
|
||||
if utils.IsSpaceRoot(sRes.GetInfo()) {
|
||||
opaque = utils.SpaceGrantOpaque()
|
||||
}
|
||||
opaque = utils.AppendJSONToOpaque(opaque, "resourceid", share.GetResourceId())
|
||||
opaque = utils.AppendPlainToOpaque(opaque, "resourcename", sRes.GetInfo().GetName())
|
||||
if user := share.GetGrantee().GetUserId(); user != nil {
|
||||
opaque = utils.AppendJSONToOpaque(opaque, "granteeuserid", user)
|
||||
} else {
|
||||
opaque = utils.AppendJSONToOpaque(opaque, "granteegroupid", share.GetGrantee().GetGroupId())
|
||||
}
|
||||
|
||||
return &collaboration.RemoveShareResponse{
|
||||
Opaque: opaque,
|
||||
Status: status.NewOK(ctx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetShare(ctx context.Context, req *collaboration.GetShareRequest) (*collaboration.GetShareResponse, error) {
|
||||
share, err := s.sm.GetShare(ctx, req.Ref)
|
||||
if err != nil {
|
||||
var st *rpc.Status
|
||||
switch err.(type) {
|
||||
case errtypes.IsNotFound:
|
||||
st = status.NewNotFound(ctx, err.Error())
|
||||
default:
|
||||
st = status.NewInternal(ctx, err.Error())
|
||||
}
|
||||
return &collaboration.GetShareResponse{
|
||||
Status: st,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &collaboration.GetShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: share,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ListShares(ctx context.Context, req *collaboration.ListSharesRequest) (*collaboration.ListSharesResponse, error) {
|
||||
shares, err := s.sm.ListShares(ctx, req.Filters) // TODO(labkode): add filter to share manager
|
||||
if err != nil {
|
||||
return &collaboration.ListSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error listing shares"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &collaboration.ListSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: shares,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShareRequest) (*collaboration.UpdateShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
user := ctxpkg.ContextMustGetUser(ctx)
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if the user has the permission to create shares at all
|
||||
ok, err := utils.CheckPermission(ctx, permission.WriteShare, gatewayClient)
|
||||
if err != nil {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed check user permission to write share"),
|
||||
}, err
|
||||
}
|
||||
if !ok {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to create user share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read share from backend. We need the shared resource's id for STATing it, it might not be in
|
||||
// the incoming request
|
||||
currentShare, err := s.sm.GetShare(ctx,
|
||||
&collaboration.ShareReference{
|
||||
Spec: &collaboration.ShareReference_Id{
|
||||
Id: req.GetShare().GetId(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var st *rpc.Status
|
||||
switch err.(type) {
|
||||
case errtypes.IsNotFound:
|
||||
st = status.NewNotFound(ctx, err.Error())
|
||||
default:
|
||||
st = status.NewInternal(ctx, err.Error())
|
||||
}
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: st,
|
||||
}, nil
|
||||
}
|
||||
|
||||
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: currentShare.GetResourceId()}})
|
||||
if err != nil {
|
||||
log.Err(err).Interface("resource_id", req.GetShare().GetResourceId()).Msg("failed to stat resource to share")
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to stat shared resource"),
|
||||
}, err
|
||||
}
|
||||
// the requesting user needs to be either the Owner/Creator of the share or have the UpdateGrant permissions on the Resource
|
||||
switch {
|
||||
case utils.UserEqual(user.GetId(), currentShare.GetCreator()) || utils.UserEqual(user.GetId(), currentShare.GetOwner()):
|
||||
fallthrough
|
||||
case sRes.GetInfo().GetPermissionSet().UpdateGrant:
|
||||
break
|
||||
default:
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "no permission to remove grants on shared resource"),
|
||||
}, err
|
||||
}
|
||||
|
||||
// resharing is forbidden for not space roots
|
||||
if !utils.IsSpaceRoot(sRes.GetInfo()) {
|
||||
// Resharing of Files/Directories is forbidden. So the grants must not allow the "grant" permissions
|
||||
if HasGrantPermissions(req.GetShare().GetPermissions().GetPermissions()) {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewInvalidArg(ctx, "resharing not supported"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
// If this is a permissions update, check if user's permissions on the resource are sufficient to set the desired permissions
|
||||
var newPermissions *provider.ResourcePermissions
|
||||
if slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathPermissions) {
|
||||
newPermissions = req.GetShare().GetPermissions().GetPermissions()
|
||||
} else {
|
||||
newPermissions = req.GetField().GetPermissions().GetPermissions()
|
||||
}
|
||||
if newPermissions != nil && !conversions.SufficientCS3Permissions(sRes.GetInfo().GetPermissionSet(), newPermissions) {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "insufficient permissions to create that kind of share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// For space root shares, if the new permissions would strip AddGrant or RemoveGrant from this share,
|
||||
// ensure at least one other share still has both so the space retains a manager.
|
||||
if utils.IsSpaceRoot(sRes.GetInfo()) && newPermissions != nil && (!newPermissions.GetAddGrant() || !newPermissions.GetRemoveGrant()) {
|
||||
if ok, err := s.spaceRootHasRemainingManager(ctx, currentShare.GetResourceId(), currentShare.GetId().GetOpaqueId()); err != nil {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewInternal(ctx, "failed to list shares for space root"),
|
||||
}, nil
|
||||
} else if !ok {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewPermissionDenied(ctx, nil, "cannot remove the last share with manager permissions on a space root"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// check if the requested permission are plausible for the Resource
|
||||
// do we need more here?
|
||||
if sRes.GetInfo().GetType() == provider.ResourceType_RESOURCE_TYPE_FILE {
|
||||
if newPermissions.GetCreateContainer() || newPermissions.GetMove() || newPermissions.GetDelete() {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewInvalid(ctx, "cannot set the requested permissions on that type of resource"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
share, err := s.sm.UpdateShare(ctx, req.Ref, req.Field.GetPermissions(), req.Share, req.UpdateMask) // TODO(labkode): check what to update
|
||||
if err != nil {
|
||||
return &collaboration.UpdateShareResponse{
|
||||
Status: status.NewInternal(ctx, "error updating share"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var opaque *typesv1beta1.Opaque
|
||||
if utils.IsSpaceRoot(sRes.GetInfo()) {
|
||||
opaque = utils.SpaceGrantOpaque()
|
||||
}
|
||||
|
||||
res := &collaboration.UpdateShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: share,
|
||||
Opaque: utils.AppendPlainToOpaque(opaque, "resourcename", sRes.GetInfo().GetName()),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) ListReceivedShares(ctx context.Context, req *collaboration.ListReceivedSharesRequest) (*collaboration.ListReceivedSharesResponse, error) {
|
||||
// For the UI add a filter to not display the denial shares
|
||||
foundExclude := false
|
||||
for _, f := range req.Filters {
|
||||
if f.Type == collaboration.Filter_TYPE_EXCLUDE_DENIALS {
|
||||
foundExclude = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundExclude {
|
||||
req.Filters = append(req.Filters, &collaboration.Filter{Type: collaboration.Filter_TYPE_EXCLUDE_DENIALS})
|
||||
}
|
||||
|
||||
var uid userpb.UserId
|
||||
_ = utils.ReadJSONFromOpaque(req.Opaque, "userid", &uid)
|
||||
shares, err := s.sm.ListReceivedShares(ctx, req.Filters, &uid) // TODO(labkode): check what to update
|
||||
if err != nil {
|
||||
return &collaboration.ListReceivedSharesResponse{
|
||||
Status: status.NewInternal(ctx, "error listing received shares"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
res := &collaboration.ListReceivedSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: shares,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) GetReceivedShare(ctx context.Context, req *collaboration.GetReceivedShareRequest) (*collaboration.GetReceivedShareResponse, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
share, err := s.sm.GetReceivedShare(ctx, req.Ref)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("error getting received share")
|
||||
switch err.(type) {
|
||||
case errtypes.NotFound:
|
||||
return &collaboration.GetReceivedShareResponse{
|
||||
Status: status.NewNotFound(ctx, "error getting received share"),
|
||||
}, nil
|
||||
default:
|
||||
return &collaboration.GetReceivedShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting received share"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
res := &collaboration.GetReceivedShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: share,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *service) UpdateReceivedShare(ctx context.Context, req *collaboration.UpdateReceivedShareRequest) (*collaboration.UpdateReceivedShareResponse, error) {
|
||||
if req.GetShare().GetShare().GetId().GetOpaqueId() == "" {
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewInvalid(ctx, "share id empty"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
isStateTransitionShareAccepted := slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathState) && req.GetShare().GetState() == collaboration.ShareState_SHARE_STATE_ACCEPTED
|
||||
isMountPointSet := slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathMountPoint) && req.GetShare().GetMountPoint().GetPath() != ""
|
||||
// we calculate a valid mountpoint only if the share should be accepted and the mount point is not set explicitly
|
||||
if isStateTransitionShareAccepted && !isMountPointSet {
|
||||
s, err := s.setReceivedShareMountPoint(ctx, req)
|
||||
switch {
|
||||
case err != nil:
|
||||
fallthrough
|
||||
case s.GetCode() != rpc.Code_CODE_OK:
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: s,
|
||||
}, err
|
||||
}
|
||||
}
|
||||
|
||||
var uid userpb.UserId
|
||||
_ = utils.ReadJSONFromOpaque(req.Opaque, "userid", &uid)
|
||||
updatedShare, err := s.sm.UpdateReceivedShare(ctx, req.Share, req.UpdateMask, &uid)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: updatedShare,
|
||||
}, nil
|
||||
case errtypes.NotFound:
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewNotFound(ctx, "error getting received share"),
|
||||
}, nil
|
||||
default:
|
||||
return &collaboration.UpdateReceivedShareResponse{
|
||||
Status: status.NewInternal(ctx, "error getting received share"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) setReceivedShareMountPoint(ctx context.Context, req *collaboration.UpdateReceivedShareRequest) (*rpc.Status, error) {
|
||||
gwc, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
receivedShare, err := gwc.GetReceivedShare(ctx, &collaboration.GetReceivedShareRequest{
|
||||
Ref: &collaboration.ShareReference{
|
||||
Spec: &collaboration.ShareReference_Id{
|
||||
Id: req.GetShare().GetShare().GetId(),
|
||||
},
|
||||
},
|
||||
})
|
||||
switch {
|
||||
case err != nil:
|
||||
fallthrough
|
||||
case receivedShare.GetStatus().GetCode() != rpc.Code_CODE_OK:
|
||||
return receivedShare.GetStatus(), err
|
||||
}
|
||||
|
||||
if receivedShare.GetShare().GetMountPoint().GetPath() != "" {
|
||||
return status.NewOK(ctx), nil
|
||||
}
|
||||
|
||||
gwc, err = s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceStat, err := gwc.Stat(ctx, &provider.StatRequest{
|
||||
Ref: &provider.Reference{
|
||||
ResourceId: receivedShare.GetShare().GetShare().GetResourceId(),
|
||||
},
|
||||
})
|
||||
switch {
|
||||
case err != nil:
|
||||
fallthrough
|
||||
case resourceStat.GetStatus().GetCode() != rpc.Code_CODE_OK:
|
||||
return resourceStat.GetStatus(), err
|
||||
}
|
||||
|
||||
// handle mount point related updates
|
||||
{
|
||||
var userID *userpb.UserId
|
||||
_ = utils.ReadJSONFromOpaque(req.Opaque, "userid", &userID)
|
||||
|
||||
receivedShares, err := s.sm.ListReceivedShares(ctx, []*collaboration.Filter{}, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if the requested mount point is available and if not, find a suitable one
|
||||
availableMountpoint, _, err := getMountpointAndUnmountedShares(ctx, receivedShares, s.gatewaySelector, nil,
|
||||
resourceStat.GetInfo().GetId(),
|
||||
resourceStat.GetInfo().GetName(),
|
||||
)
|
||||
if err != nil {
|
||||
return status.NewInternal(ctx, err.Error()), nil
|
||||
}
|
||||
|
||||
if !slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathMountPoint) {
|
||||
req.GetUpdateMask().Paths = append(req.GetUpdateMask().GetPaths(), _fieldMaskPathMountPoint)
|
||||
}
|
||||
|
||||
req.GetShare().MountPoint = &provider.Reference{
|
||||
Path: availableMountpoint,
|
||||
}
|
||||
}
|
||||
|
||||
return status.NewOK(ctx), nil
|
||||
}
|
||||
|
||||
// GetMountpointAndUnmountedShares returns a new or existing mountpoint for the given info and produces a list of unmounted received shares for the same resource
|
||||
func GetMountpointAndUnmountedShares(ctx context.Context, gwc gateway.GatewayAPIClient, id *provider.ResourceId, name string, userId *userpb.UserId) (string, []*collaboration.ReceivedShare, error) {
|
||||
listReceivedSharesReq := &collaboration.ListReceivedSharesRequest{}
|
||||
if userId != nil {
|
||||
listReceivedSharesReq.Opaque = utils.AppendJSONToOpaque(nil, "userid", userId)
|
||||
}
|
||||
listReceivedSharesRes, err := gwc.ListReceivedShares(ctx, listReceivedSharesReq)
|
||||
if err != nil {
|
||||
return "", nil, errtypes.InternalError("grpc list received shares request failed")
|
||||
}
|
||||
|
||||
if err := errtypes.NewErrtypeFromStatus(listReceivedSharesRes.GetStatus()); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return getMountpointAndUnmountedShares(ctx, listReceivedSharesRes.GetShares(), nil, gwc, id, name)
|
||||
}
|
||||
|
||||
// GetMountpointAndUnmountedShares returns a new or existing mountpoint for the given info and produces a list of unmounted received shares for the same resource
|
||||
func getMountpointAndUnmountedShares(ctx context.Context, receivedShares []*collaboration.ReceivedShare, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], gwc gateway.GatewayAPIClient, id *provider.ResourceId, name string) (string, []*collaboration.ReceivedShare, error) {
|
||||
|
||||
unmountedShares := []*collaboration.ReceivedShare{}
|
||||
base := filepath.Clean(name)
|
||||
mount := base
|
||||
existingMountpoint := ""
|
||||
mountedShares := make([]string, 0, len(receivedShares))
|
||||
var pathExists bool
|
||||
var err error
|
||||
|
||||
for _, s := range receivedShares {
|
||||
resourceIDEqual := utils.ResourceIDEqual(s.GetShare().GetResourceId(), id)
|
||||
|
||||
if resourceIDEqual && s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
|
||||
if gatewaySelector != nil {
|
||||
gwc, err = gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
// a share to the resource already exists and is mounted, remembers the mount point
|
||||
_, err := utils.GetResourceByID(ctx, s.GetShare().GetResourceId(), gwc)
|
||||
if err == nil {
|
||||
existingMountpoint = s.GetMountPoint().GetPath()
|
||||
}
|
||||
}
|
||||
|
||||
if resourceIDEqual && s.State != collaboration.ShareState_SHARE_STATE_ACCEPTED {
|
||||
// a share to the resource already exists but is not mounted, collect the unmounted share
|
||||
unmountedShares = append(unmountedShares, s)
|
||||
}
|
||||
|
||||
if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
|
||||
// collect all accepted mount points
|
||||
mountedShares = append(mountedShares, s.GetMountPoint().GetPath())
|
||||
if s.GetMountPoint().GetPath() == mount {
|
||||
// does the shared resource still exist?
|
||||
if gatewaySelector != nil {
|
||||
gwc, err = gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
_, err := utils.GetResourceByID(ctx, s.GetShare().GetResourceId(), gwc)
|
||||
if err == nil {
|
||||
pathExists = true
|
||||
}
|
||||
// TODO we could delete shares here if the stat returns code NOT FOUND ... but listening for file deletes would be better
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if existingMountpoint != "" {
|
||||
// we want to reuse the same mountpoint for all unmounted shares to the same resource
|
||||
return existingMountpoint, unmountedShares, nil
|
||||
}
|
||||
|
||||
// If the mount point really already exists, we need to insert a number into the filename
|
||||
if pathExists {
|
||||
// now we have a list of shares, we want to iterate over all of them and check for name collisions agents a mount points list
|
||||
for i := 1; i <= len(mountedShares)+1; i++ {
|
||||
ext := filepath.Ext(base)
|
||||
name := strings.TrimSuffix(base, ext)
|
||||
|
||||
mount = name + " (" + strconv.Itoa(i) + ")" + ext
|
||||
if !slices.Contains(mountedShares, mount) {
|
||||
return mount, unmountedShares, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mount, unmountedShares, nil
|
||||
}
|
||||
Reference in New Issue
Block a user