github.com/cs3org/reva/v2@v2.27.7/internal/grpc/interceptors/eventsmiddleware/events.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package eventsmiddleware
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  
    25  	user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    26  	ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
    27  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    28  	collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
    29  	link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
    30  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    31  	"github.com/cs3org/reva/v2/pkg/appctx"
    32  	revactx "github.com/cs3org/reva/v2/pkg/ctx"
    33  	"github.com/cs3org/reva/v2/pkg/events"
    34  	"github.com/cs3org/reva/v2/pkg/events/stream"
    35  	"github.com/cs3org/reva/v2/pkg/rgrpc"
    36  	"github.com/cs3org/reva/v2/pkg/storagespace"
    37  	"github.com/cs3org/reva/v2/pkg/utils"
    38  	"github.com/mitchellh/mapstructure"
    39  	"google.golang.org/grpc"
    40  )
    41  
    42  const (
    43  	defaultPriority = 200
    44  )
    45  
    46  func init() {
    47  	rgrpc.RegisterUnaryInterceptor("eventsmiddleware", NewUnary)
    48  }
    49  
    50  // NewUnary returns a new unary interceptor that emits events when needed
    51  // no lint because of the switch statement that should be extendable
    52  //
    53  //nolint:gocritic
    54  func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error) {
    55  	publisher, err := publisherFromConfig(m)
    56  	if err != nil {
    57  		return nil, 0, err
    58  	}
    59  
    60  	interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    61  		// Register a channel in the context to receive the space owner id from the handler(s) further down the stack
    62  		var ownerID *user.UserId
    63  		sendOwnerChan := make(chan *user.UserId)
    64  		ctx = storagespace.ContextRegisterSendOwnerChan(ctx, sendOwnerChan)
    65  
    66  		res, err := handler(ctx, req)
    67  		if err != nil {
    68  			return res, err
    69  		}
    70  
    71  		// Read the space owner id from the channel
    72  		select {
    73  		case ownerID = <-sendOwnerChan:
    74  		default:
    75  		}
    76  
    77  		executant, _ := revactx.ContextGetUser(ctx)
    78  
    79  		// The MoveResponse event is moved to the decomposedfs
    80  		var ev interface{}
    81  		switch v := res.(type) {
    82  		case *collaboration.CreateShareResponse:
    83  			if isSuccess(v) {
    84  				ev = ShareCreated(v, executant)
    85  			}
    86  		case *collaboration.RemoveShareResponse:
    87  			if isSuccess(v) {
    88  				ev = ShareRemoved(v, req.(*collaboration.RemoveShareRequest), executant)
    89  			}
    90  		case *collaboration.UpdateShareResponse:
    91  			if isSuccess(v) {
    92  				ev = ShareUpdated(v, req.(*collaboration.UpdateShareRequest), executant)
    93  			}
    94  		case *collaboration.UpdateReceivedShareResponse:
    95  			if isSuccess(v) {
    96  				ev = ReceivedShareUpdated(v, executant)
    97  			}
    98  		case *link.CreatePublicShareResponse:
    99  			if isSuccess(v) {
   100  				ev = LinkCreated(v, executant)
   101  			}
   102  		case *link.UpdatePublicShareResponse:
   103  			if isSuccess(v) {
   104  				ev = LinkUpdated(v, req.(*link.UpdatePublicShareRequest), executant)
   105  			}
   106  		case *link.RemovePublicShareResponse:
   107  			if isSuccess(v) {
   108  				ev = LinkRemoved(v, req.(*link.RemovePublicShareRequest), executant)
   109  			}
   110  		case *link.GetPublicShareByTokenResponse:
   111  			if isSuccess(v) {
   112  				ev = LinkAccessed(v, executant)
   113  			} else {
   114  				ev = LinkAccessFailed(v, req.(*link.GetPublicShareByTokenRequest), executant)
   115  			}
   116  		case *ocmcore.CreateOCMCoreShareResponse:
   117  			if isSuccess(v) {
   118  				ev = OCMCoreShareCreated(v, req.(*ocmcore.CreateOCMCoreShareRequest), executant)
   119  			}
   120  		case *provider.AddGrantResponse:
   121  			// TODO: update CS3 APIs
   122  			// FIXME these should be part of the RemoveGrantRequest object
   123  			// https://github.com/owncloud/ocis/issues/4312
   124  			r := req.(*provider.AddGrantRequest)
   125  			if isSuccess(v) && utils.ExistsInOpaque(r.Opaque, "spacegrant") {
   126  				ev = SpaceShared(v, r, executant)
   127  			}
   128  		case *provider.UpdateGrantResponse:
   129  			r := req.(*provider.UpdateGrantRequest)
   130  			if isSuccess(v) && utils.ExistsInOpaque(r.Opaque, "spacegrant") {
   131  				ev = SpaceShareUpdated(v, r, executant)
   132  			}
   133  		case *provider.RemoveGrantResponse:
   134  			r := req.(*provider.RemoveGrantRequest)
   135  			if isSuccess(v) && utils.ExistsInOpaque(r.Opaque, "spacegrant") {
   136  				ev = SpaceUnshared(v, req.(*provider.RemoveGrantRequest), executant)
   137  			}
   138  		case *provider.CreateContainerResponse:
   139  			if isSuccess(v) {
   140  				ev = ContainerCreated(v, req.(*provider.CreateContainerRequest), ownerID, executant)
   141  			}
   142  		case *provider.InitiateFileDownloadResponse:
   143  			if isSuccess(v) {
   144  				ev = FileDownloaded(v, req.(*provider.InitiateFileDownloadRequest), executant)
   145  			}
   146  		case *provider.DeleteResponse:
   147  			if isSuccess(v) {
   148  				ev = ItemTrashed(v, req.(*provider.DeleteRequest), ownerID, executant)
   149  			}
   150  		case *provider.PurgeRecycleResponse:
   151  			if isSuccess(v) {
   152  				ev = ItemPurged(v, req.(*provider.PurgeRecycleRequest), executant)
   153  			}
   154  		case *provider.RestoreRecycleItemResponse:
   155  			if isSuccess(v) {
   156  				ev = ItemRestored(v, req.(*provider.RestoreRecycleItemRequest), ownerID, executant)
   157  			}
   158  		case *provider.RestoreFileVersionResponse:
   159  			if isSuccess(v) {
   160  				ev = FileVersionRestored(v, req.(*provider.RestoreFileVersionRequest), ownerID, executant)
   161  			}
   162  		case *provider.CreateStorageSpaceResponse:
   163  			if isSuccess(v) && v.StorageSpace != nil { // TODO: Why are there CreateStorageSpaceResponses with nil StorageSpace?
   164  				ev = SpaceCreated(v, executant)
   165  			}
   166  		case *provider.UpdateStorageSpaceResponse:
   167  			if isSuccess(v) {
   168  				r := req.(*provider.UpdateStorageSpaceRequest)
   169  				if r.StorageSpace.Name != "" {
   170  					ev = SpaceRenamed(v, r, executant)
   171  				} else if utils.ExistsInOpaque(r.Opaque, "restore") {
   172  					ev = SpaceEnabled(v, r, executant)
   173  				} else {
   174  					ev = SpaceUpdated(v, r, executant)
   175  				}
   176  			}
   177  		case *provider.DeleteStorageSpaceResponse:
   178  			if isSuccess(v) {
   179  				r := req.(*provider.DeleteStorageSpaceRequest)
   180  				if utils.ExistsInOpaque(r.Opaque, "purge") {
   181  					ev = SpaceDeleted(v, r, executant)
   182  				} else {
   183  					ev = SpaceDisabled(v, r, executant)
   184  				}
   185  			}
   186  		case *provider.TouchFileResponse:
   187  			if isSuccess(v) {
   188  				ev = FileTouched(v, req.(*provider.TouchFileRequest), ownerID, executant)
   189  			}
   190  		case *provider.SetLockResponse:
   191  			if isSuccess(v) {
   192  				ev = FileLocked(v, req.(*provider.SetLockRequest), ownerID, executant)
   193  			}
   194  		case *provider.UnlockResponse:
   195  			if isSuccess(v) {
   196  				ev = FileUnlocked(v, req.(*provider.UnlockRequest), ownerID, executant)
   197  			}
   198  		}
   199  
   200  		if ev != nil {
   201  			if err := events.Publish(ctx, publisher, ev); err != nil {
   202  				appctx.GetLogger(ctx).Error().Err(err).Interface("event", ev).Msg("publishing event failed")
   203  			}
   204  		}
   205  
   206  		return res, nil
   207  	}
   208  	return interceptor, defaultPriority, nil
   209  }
   210  
   211  // NewStream returns a new server stream interceptor
   212  // that creates the application context.
   213  func NewStream() grpc.StreamServerInterceptor {
   214  	interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
   215  		// TODO: Use ss.RecvMsg() and ss.SendMsg() to send events from a stream
   216  		return handler(srv, ss)
   217  	}
   218  	return interceptor
   219  }
   220  
   221  // common interface to all responses
   222  type su interface {
   223  	GetStatus() *rpc.Status
   224  }
   225  
   226  func isSuccess(res su) bool {
   227  	return res.GetStatus().Code == rpc.Code_CODE_OK
   228  }
   229  
   230  func publisherFromConfig(m map[string]interface{}) (events.Publisher, error) {
   231  	typ := m["type"].(string)
   232  	switch typ {
   233  	default:
   234  		return nil, fmt.Errorf("stream type '%s' not supported", typ)
   235  	case "nats":
   236  		var cfg stream.NatsConfig
   237  		if err := mapstructure.Decode(m, &cfg); err != nil {
   238  			return nil, err
   239  		}
   240  		name, _ := m["name"].(string)
   241  		return stream.NatsFromConfig(name, false, cfg)
   242  	}
   243  }