diff --git a/changelog/unreleased/sharing-events.md b/changelog/unreleased/sharing-events.md new file mode 100644 index 0000000000..e203e089ad --- /dev/null +++ b/changelog/unreleased/sharing-events.md @@ -0,0 +1,6 @@ +Enhancement: Add events for sharing action + +Includes lifecycle events for shares and public links doesn't include federated sharing events for now +see full list of events in `pkg/events/types.go` + +https://github.com/cs3org/reva/pull/2627 diff --git a/internal/grpc/interceptors/eventsmiddleware/conversion.go b/internal/grpc/interceptors/eventsmiddleware/conversion.go index 78989557fa..4d637945ca 100644 --- a/internal/grpc/interceptors/eventsmiddleware/conversion.go +++ b/internal/grpc/interceptors/eventsmiddleware/conversion.go @@ -20,18 +20,124 @@ package eventsmiddleware import ( collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" "github.com/cs3org/reva/v2/pkg/events" ) -// ShareCreated converts response to event +// ShareCreated converts the response to an event func ShareCreated(r *collaboration.CreateShareResponse) events.ShareCreated { - e := events.ShareCreated{ + return events.ShareCreated{ Sharer: r.Share.Creator, GranteeUserID: r.Share.GetGrantee().GetUserId(), GranteeGroupID: r.Share.GetGrantee().GetGroupId(), ItemID: r.Share.ResourceId, CTime: r.Share.Ctime, + Permissions: r.Share.Permissions, } +} + +// ShareRemoved converts the response to an event +func ShareRemoved(r *collaboration.RemoveShareResponse, req *collaboration.RemoveShareRequest) events.ShareRemoved { + return events.ShareRemoved{ + ShareID: req.Ref.GetId(), + ShareKey: req.Ref.GetKey(), + } +} + +// ShareUpdated converts the response to an event +func ShareUpdated(r *collaboration.UpdateShareResponse, req *collaboration.UpdateShareRequest) events.ShareUpdated { + updated := "" + if req.Field.GetPermissions() != nil { + updated = "permissions" + } else if req.Field.GetDisplayName() != "" { + updated = "displayname" + } + return events.ShareUpdated{ + ShareID: r.Share.Id, + ItemID: r.Share.ResourceId, + Permissions: r.Share.Permissions, + GranteeUserID: r.Share.GetGrantee().GetUserId(), + GranteeGroupID: r.Share.GetGrantee().GetGroupId(), + Sharer: r.Share.Creator, + MTime: r.Share.Mtime, + Updated: updated, + } +} + +// ReceivedShareUpdated converts the response to an event +func ReceivedShareUpdated(r *collaboration.UpdateReceivedShareResponse) events.ReceivedShareUpdated { + return events.ReceivedShareUpdated{ + 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) events.LinkCreated { + return events.LinkCreated{ + 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, + } +} + +// LinkUpdated converts the response to an event +func LinkUpdated(r *link.UpdatePublicShareResponse, req *link.UpdatePublicShareRequest) events.LinkUpdated { + return events.LinkUpdated{ + 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, + FieldUpdated: link.UpdatePublicShareRequest_Update_Type_name[int32(req.Update.GetType())], + } +} - return e +// LinkAccessed converts the response to an event +func LinkAccessed(r *link.GetPublicShareByTokenResponse) events.LinkAccessed { + return events.LinkAccessed{ + 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) events.LinkAccessFailed { + return events.LinkAccessFailed{ + ShareID: r.Share.Id, + Token: r.Share.Token, + Status: r.Status.Code, + Message: r.Status.Message, + } +} + +// LinkRemoved converts the response to an event +func LinkRemoved(r *link.RemovePublicShareResponse, req *link.RemovePublicShareRequest) events.LinkRemoved { + return events.LinkRemoved{ + ShareID: req.Ref.GetId(), + ShareToken: req.Ref.GetToken(), + } } diff --git a/internal/grpc/interceptors/eventsmiddleware/events.go b/internal/grpc/interceptors/eventsmiddleware/events.go index d9c05890fe..5ba24ef4cf 100644 --- a/internal/grpc/interceptors/eventsmiddleware/events.go +++ b/internal/grpc/interceptors/eventsmiddleware/events.go @@ -27,7 +27,9 @@ import ( "github.com/asim/go-micro/plugins/events/nats/v4" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + v1beta12 "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" "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/events/server" "github.com/cs3org/reva/v2/pkg/rgrpc" @@ -59,9 +61,39 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error var ev interface{} switch v := res.(type) { case *collaboration.CreateShareResponse: - if v.Status.Code == rpc.Code_CODE_OK { + if isSuccess(v) { ev = ShareCreated(v) } + case *collaboration.RemoveShareResponse: + if isSuccess(v) { + ev = ShareRemoved(v, req.(*collaboration.RemoveShareRequest)) + } + case *collaboration.UpdateShareResponse: + if isSuccess(v) { + ev = ShareUpdated(v, req.(*collaboration.UpdateShareRequest)) + } + case *collaboration.UpdateReceivedShareResponse: + if isSuccess(v) { + ev = ReceivedShareUpdated(v) + } + case *link.CreatePublicShareResponse: + if isSuccess(v) { + ev = LinkCreated(v) + } + case *link.UpdatePublicShareResponse: + if isSuccess(v) { + ev = LinkUpdated(v, req.(*link.UpdatePublicShareRequest)) + } + case *link.RemovePublicShareResponse: + if isSuccess(v) { + ev = LinkRemoved(v, req.(*link.RemovePublicShareRequest)) + } + case *link.GetPublicShareByTokenResponse: + if isSuccess(v) { + ev = LinkAccessed(v) + } else { + ev = LinkAccessFailed(v, req.(*link.GetPublicShareByTokenRequest)) + } } if ev != nil { @@ -85,6 +117,15 @@ func NewStream() grpc.StreamServerInterceptor { return interceptor } +// common interface to all responses +type su interface { + GetStatus() *v1beta12.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 { diff --git a/pkg/events/example/consumer/consumer.go b/pkg/events/example/consumer/consumer.go index a5a347f3c5..f6cf9f2f77 100644 --- a/pkg/events/example/consumer/consumer.go +++ b/pkg/events/example/consumer/consumer.go @@ -34,8 +34,15 @@ func Example(c events.Consumer) { // Step 2 - which events does the consumer listen too? evs := []events.Unmarshaller{ - // for example created shares events.ShareCreated{}, + events.ShareUpdated{}, + events.ShareRemoved{}, + events.ReceivedShareUpdated{}, + events.LinkCreated{}, + events.LinkUpdated{}, + events.LinkRemoved{}, + events.LinkAccessed{}, + events.LinkAccessFailed{}, } // Step 3 - create event channel @@ -53,7 +60,7 @@ func Example(c events.Consumer) { case events.ShareCreated: fmt.Printf("%s) Share created: %+v\n", group, v) default: - fmt.Printf("%s) Unregistered event: %+v\n", group, v) + fmt.Printf("%s) %T: %+v\n", group, v, v) } } diff --git a/pkg/events/types.go b/pkg/events/types.go index 3ca9dd7f80..f6ee9a5072 100644 --- a/pkg/events/types.go +++ b/pkg/events/types.go @@ -23,18 +23,22 @@ import ( group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" user "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" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) // ShareCreated is emitted when a share is created -type ShareCreated struct { // TODO: Rename to ShareCreatedEvent? +type ShareCreated struct { Sharer *user.UserId // split the protobuf Grantee oneof so we can use stdlib encoding/json GranteeUserID *user.UserId GranteeGroupID *group.GroupId Sharee *provider.Grantee ItemID *provider.ResourceId + Permissions *collaboration.SharePermissions CTime *types.Timestamp } @@ -44,3 +48,149 @@ func (ShareCreated) Unmarshal(v []byte) (interface{}, error) { err := json.Unmarshal(v, &e) return e, err } + +// ShareRemoved is emitted when a share is removed +type ShareRemoved struct { + // split protobuf Spec + ShareID *collaboration.ShareId + ShareKey *collaboration.ShareKey +} + +// Unmarshal to fulfill umarshaller interface +func (ShareRemoved) Unmarshal(v []byte) (interface{}, error) { + e := ShareRemoved{} + err := json.Unmarshal(v, &e) + return e, err +} + +// ShareUpdated is emitted when a share is updated +type ShareUpdated struct { + ShareID *collaboration.ShareId + ItemID *provider.ResourceId + Permissions *collaboration.SharePermissions + GranteeUserID *user.UserId + GranteeGroupID *group.GroupId + Sharer *user.UserId + MTime *types.Timestamp + + // indicates what was updated - one of "displayname", "permissions" + Updated string +} + +// Unmarshal to fulfill umarshaller interface +func (ShareUpdated) Unmarshal(v []byte) (interface{}, error) { + e := ShareUpdated{} + err := json.Unmarshal(v, &e) + return e, err +} + +// ReceivedShareUpdated is emitted when a received share is accepted or declined +type ReceivedShareUpdated struct { + ShareID *collaboration.ShareId + ItemID *provider.ResourceId + Permissions *collaboration.SharePermissions + GranteeUserID *user.UserId + GranteeGroupID *group.GroupId + Sharer *user.UserId + MTime *types.Timestamp + + State string +} + +// Unmarshal to fulfill umarshaller interface +func (ReceivedShareUpdated) Unmarshal(v []byte) (interface{}, error) { + e := ReceivedShareUpdated{} + err := json.Unmarshal(v, &e) + return e, err +} + +// LinkCreated is emitted when a public link is created +type LinkCreated struct { + ShareID *link.PublicShareId + Sharer *user.UserId + ItemID *provider.ResourceId + Permissions *link.PublicSharePermissions + DisplayName string + Expiration *types.Timestamp + PasswordProtected bool + CTime *types.Timestamp + Token string +} + +// Unmarshal to fulfill umarshaller interface +func (LinkCreated) Unmarshal(v []byte) (interface{}, error) { + e := LinkCreated{} + err := json.Unmarshal(v, &e) + return e, err +} + +// LinkUpdated is emitted when a public link is updated +type LinkUpdated struct { + ShareID *link.PublicShareId + Sharer *user.UserId + ItemID *provider.ResourceId + Permissions *link.PublicSharePermissions + DisplayName string + Expiration *types.Timestamp + PasswordProtected bool + CTime *types.Timestamp + Token string + + FieldUpdated string +} + +// Unmarshal to fulfill umarshaller interface +func (LinkUpdated) Unmarshal(v []byte) (interface{}, error) { + e := LinkUpdated{} + err := json.Unmarshal(v, &e) + return e, err +} + +// LinkAccessed is emitted when a public link is accessed successfully (by token) +type LinkAccessed struct { + ShareID *link.PublicShareId + Sharer *user.UserId + ItemID *provider.ResourceId + Permissions *link.PublicSharePermissions + DisplayName string + Expiration *types.Timestamp + PasswordProtected bool + CTime *types.Timestamp + Token string +} + +// Unmarshal to fulfill umarshaller interface +func (LinkAccessed) Unmarshal(v []byte) (interface{}, error) { + e := LinkAccessed{} + err := json.Unmarshal(v, &e) + return e, err +} + +// LinkAccessFailed is emitted when an access to a public link has resulted in an error (by token) +type LinkAccessFailed struct { + ShareID *link.PublicShareId + Token string + Status rpc.Code + Message string +} + +// Unmarshal to fulfill umarshaller interface +func (LinkAccessFailed) Unmarshal(v []byte) (interface{}, error) { + e := LinkAccessFailed{} + err := json.Unmarshal(v, &e) + return e, err +} + +// LinkRemoved is emitted when a share is removed +type LinkRemoved struct { + // split protobuf Ref + ShareID *link.PublicShareId + ShareToken string +} + +// Unmarshal to fulfill umarshaller interface +func (LinkRemoved) Unmarshal(v []byte) (interface{}, error) { + e := LinkRemoved{} + err := json.Unmarshal(v, &e) + return e, err +}