diff --git a/.drone.star b/.drone.star index a501e4679..0bddbb7b9 100644 --- a/.drone.star +++ b/.drone.star @@ -16,6 +16,7 @@ config = { "modules": [ # if you add a module here please also add it to the root level Makefile "accounts", + "audit", "glauth", "graph-explorer", "graph", diff --git a/Makefile b/Makefile index de9c6924b..2bffcac21 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ L10N_MODULES := $(shell find . -path '*.tx*' -name 'config' | sed 's|/[^/]*$$||' # if you add a module here please also add it to the .drone.star file OCIS_MODULES = \ accounts \ + audit \ glauth \ graph \ graph-explorer \ diff --git a/audit/Makefile b/audit/Makefile new file mode 100644 index 000000000..b2661463c --- /dev/null +++ b/audit/Makefile @@ -0,0 +1,55 @@ +SHELL := bash +NAME := audit + +include ../.make/recursion.mk + +.PHONY: test-acceptance-webui +test-acceptance-webui: + ./ui/tests/run-acceptance-test.sh $(FEATURE_PATH) + + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../.make/go.mk + +############ release ############ +include ../.make/release.mk + +############ docs generate ############ +include ../.make/docs.mk + +############ l10n ############ +include ../.make/l10n.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate \ + grpc-docs-generate + +############ generate ############ +include ../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: protobuf # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: yarn-build + +.PHONY: yarn-build +yarn-build: node_modules + yarn lint + yarn test + yarn build + +.PHONY: node_modules +node_modules: + yarn install --immutable + +############ protobuf ############ +include ../.make/protobuf.mk + +.PHONY: protobuf +protobuf: buf-generate diff --git a/audit/pkg/service/service.go b/audit/pkg/service/service.go index 4f1f4b1aa..905e251ee 100644 --- a/audit/pkg/service/service.go +++ b/audit/pkg/service/service.go @@ -45,6 +45,22 @@ func StartAuditLogger(ctx context.Context, ch <-chan interface{}, log log.Logger switch ev := i.(type) { case events.ShareCreated: auditEvent = types.ShareCreated(ev) + case events.LinkCreated: + auditEvent = types.LinkCreated(ev) + case events.ShareUpdated: + auditEvent = types.ShareUpdated(ev) + case events.LinkUpdated: + auditEvent = types.LinkUpdated(ev) + case events.ShareRemoved: + auditEvent = types.ShareRemoved(ev) + case events.LinkRemoved: + auditEvent = types.LinkRemoved(ev) + case events.ReceivedShareUpdated: + auditEvent = types.ReceivedShareUpdated(ev) + case events.LinkAccessed: + auditEvent = types.LinkAccessed(ev) + case events.LinkAccessFailed: + auditEvent = types.LinkAccessFailed(ev) default: log.Error().Interface("event", ev).Msg(fmt.Sprintf("can't handle event of type '%T'", ev)) continue @@ -95,5 +111,20 @@ func Marshal(format string, log log.Logger) Marshaller { return nil case "json": return json.Marshal + case "minimal": + return func(ev interface{}) ([]byte, error) { + b, err := json.Marshal(ev) + if err != nil { + return nil, err + } + + m := make(map[string]interface{}) + if err := json.Unmarshal(b, &m); err != nil { + return nil, err + } + + format := fmt.Sprintf("%s)\n %s", m["Action"], m["Message"]) + return []byte(format), nil + } } } diff --git a/audit/pkg/service/service_test.go b/audit/pkg/service/service_test.go index 0724acc39..8bdcac5cd 100644 --- a/audit/pkg/service/service_test.go +++ b/audit/pkg/service/service_test.go @@ -12,6 +12,8 @@ import ( group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/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" rtypes "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) @@ -22,37 +24,275 @@ var testCases = []struct { CheckAuditEvent func(*testing.T, []byte) }{ { - Alias: "ShareCreated", + Alias: "ShareCreated - user", SystemEvent: events.ShareCreated{ - Sharer: &user.UserId{ - OpaqueId: "sharing-userid", - Idp: "idp", - }, - GranteeUserID: &user.UserId{ - OpaqueId: "beshared-userid", - Idp: "idp", - }, - GranteeGroupID: &group.GroupId{}, - Sharee: &provider.Grantee{}, - ItemID: &provider.ResourceId{ - StorageId: "storage-1", - OpaqueId: "itemid-1", - }, - CTime: &rtypes.Timestamp{ - Seconds: 0, - Nanos: 0, - }, + Sharer: userID("sharing-userid"), + GranteeUserID: userID("beshared-userid"), + GranteeGroupID: nil, + ItemID: resourceID("storage-1", "itemid-1"), + CTime: timestamp(0), }, CheckAuditEvent: func(t *testing.T, b []byte) { ev := types.AuditEventShareCreated{} - err := json.Unmarshal(b, &ev) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(b, &ev)) - require.Equal(t, ev.User, "sharing-userid") - require.Equal(t, ev.ShareWith, "beshared-userid") - require.Equal(t, ev.FileID, "itemid-1") - require.Equal(t, ev.Time, "1970-01-01T01:00:00+01:00") + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "sharing-userid", "1970-01-01T00:00:00Z", "user 'sharing-userid' shared file 'itemid-1' with 'beshared-userid'", "file_shared") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "itemid-1", "sharing-userid", "") + // AuditEventShareCreated fields + require.Equal(t, "", ev.ItemType) + require.Equal(t, "", ev.ExpirationDate) + require.Equal(t, false, ev.SharePass) + //require.Equal(t, "stat:true ", ev.Permissions) // TODO: BUG! Should work + require.Equal(t, "user", ev.ShareType) + require.Equal(t, "beshared-userid", ev.ShareWith) + require.Equal(t, "sharing-userid", ev.ShareOwner) + require.Equal(t, "", ev.ShareToken) + }, + }, { + Alias: "ShareCreated - group", + SystemEvent: events.ShareCreated{ + Sharer: userID("sharing-userid"), + GranteeUserID: nil, + GranteeGroupID: groupID("beshared-groupid"), + ItemID: resourceID("storage-1", "itemid-1"), + CTime: timestamp(10e8), + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventShareCreated{} + require.NoError(t, json.Unmarshal(b, &ev)) + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "sharing-userid", "2001-09-09T01:46:40Z", "user 'sharing-userid' shared file 'itemid-1' with 'beshared-groupid'", "file_shared") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "itemid-1", "sharing-userid", "") + // AuditEventShareCreated fields + require.Equal(t, "", ev.ItemType) + require.Equal(t, "", ev.ExpirationDate) + require.Equal(t, false, ev.SharePass) + //require.Equal(t, "stat:true ", ev.Permissions) // TODO: BUG! Should work + require.Equal(t, "group", ev.ShareType) + require.Equal(t, "beshared-groupid", ev.ShareWith) + require.Equal(t, "sharing-userid", ev.ShareOwner) + require.Equal(t, "", ev.ShareToken) + + }, + }, { + Alias: "ShareUpdated", + SystemEvent: events.ShareUpdated{ + ShareID: shareID("shareid"), + Sharer: userID("sharing-userid"), + GranteeUserID: nil, + GranteeGroupID: groupID("beshared-groupid"), + ItemID: resourceID("storage-1", "itemid-1"), + Permissions: sharePermissions("stat", "get_quota"), + MTime: timestamp(10e8), + Updated: "permissions", + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventShareUpdated{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "sharing-userid", "2001-09-09T01:46:40Z", "user 'sharing-userid' updated field 'permissions' of share 'shareid'", "share_permission_updated") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "itemid-1", "sharing-userid", "shareid") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) // not implemented atm + require.Equal(t, "", ev.ExpirationDate) // no expiration for shares + require.Equal(t, false, ev.SharePass) + require.Equal(t, "get_quota:true stat:true ", ev.Permissions) + require.Equal(t, "group", ev.ShareType) + require.Equal(t, "beshared-groupid", ev.ShareWith) + require.Equal(t, "sharing-userid", ev.ShareOwner) + require.Equal(t, "", ev.ShareToken) // token not filled for shares + }, + }, { + Alias: "LinkUpdated - permissions", + SystemEvent: events.LinkUpdated{ + ShareID: linkID("shareid"), + Sharer: userID("sharing-userid"), + ItemID: resourceID("storage-1", "itemid-1"), + Permissions: linkPermissions("stat"), + CTime: timestamp(10e8), + DisplayName: "link", + Expiration: timestamp(10e8 + 10e5), + PasswordProtected: true, + Token: "token-123", + FieldUpdated: "permissions", + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventShareUpdated{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "sharing-userid", "2001-09-09T01:46:40Z", "user 'sharing-userid' updated field 'permissions' of public link 'shareid'", "share_permission_updated") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "itemid-1", "sharing-userid", "shareid") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) // not implemented atm + require.Equal(t, "2001-09-20T15:33:20Z", ev.ExpirationDate) + require.Equal(t, true, ev.SharePass) + require.Equal(t, "stat:true ", ev.Permissions) + require.Equal(t, "link", ev.ShareType) + require.Equal(t, "", ev.ShareWith) // not filled on links + require.Equal(t, "sharing-userid", ev.ShareOwner) + require.Equal(t, "token-123", ev.ShareToken) + }, + }, { + Alias: "ShareRemoved", + SystemEvent: events.ShareRemoved{ + ShareID: shareID("shareid"), + ShareKey: nil, + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventShareRemoved{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "", "", "share id:'shareid' uid:'' item-id:'' was removed", "file_unshared") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "", "", "shareid") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) // not implemented atm + require.Equal(t, "", ev.ShareType) + require.Equal(t, "", ev.ShareWith) // not filled on links + }, + }, { + Alias: "LinkRemoved - id", + SystemEvent: events.LinkRemoved{ + ShareID: linkID("shareid"), + ShareToken: "", + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventShareRemoved{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "", "", "public link id:'shareid' was removed", "file_unshared") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "", "", "shareid") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) // not implemented atm + require.Equal(t, "link", ev.ShareType) + require.Equal(t, "", ev.ShareWith) // not filled on links + }, + }, { + Alias: "LinkRemoved - token", + SystemEvent: events.LinkRemoved{ + ShareID: nil, + ShareToken: "token-123", + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventShareRemoved{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "", "", "public link id:'token-123' was removed", "file_unshared") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "", "", "token-123") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) // not implemented atm + require.Equal(t, "link", ev.ShareType) + require.Equal(t, "", ev.ShareWith) // not filled on links + }, + }, { + Alias: "Share accepted", + SystemEvent: events.ReceivedShareUpdated{ + ShareID: shareID("shareid"), + ItemID: resourceID("storageid-1", "itemid-1"), + Permissions: sharePermissions("get_quota"), + GranteeUserID: userID("beshared-userid"), + GranteeGroupID: nil, + Sharer: userID("sharing-userid"), + MTime: timestamp(10e8), + State: "SHARE_STATE_ACCEPTED", + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventReceivedShareUpdated{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "beshared-userid", "2001-09-09T01:46:40Z", "user 'beshared-userid' accepted share 'shareid' from user 'sharing-userid'", "share_accepted") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "itemid-1", "sharing-userid", "shareid") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) + require.Equal(t, "user", ev.ShareType) + require.Equal(t, "beshared-userid", ev.ShareWith) + }, + }, { + Alias: "Share declined", + SystemEvent: events.ReceivedShareUpdated{ + ShareID: shareID("shareid"), + ItemID: resourceID("storageid-1", "itemid-1"), + Permissions: sharePermissions("get_quota"), + GranteeUserID: userID("beshared-userid"), + GranteeGroupID: nil, + Sharer: userID("sharing-userid"), + MTime: timestamp(10e8), + State: "SHARE_STATE_DECLINED", + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventReceivedShareUpdated{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "beshared-userid", "2001-09-09T01:46:40Z", "user 'beshared-userid' declined share 'shareid' from user 'sharing-userid'", "share_declined") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "itemid-1", "sharing-userid", "shareid") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) + require.Equal(t, "user", ev.ShareType) + require.Equal(t, "beshared-userid", ev.ShareWith) + }, + }, { + Alias: "Link accessed - success", + SystemEvent: events.LinkAccessed{ + ShareID: linkID("shareid"), + Sharer: userID("sharing-userid"), + ItemID: resourceID("storage-1", "itemid-1"), + Permissions: linkPermissions("stat"), + DisplayName: "link", + Expiration: timestamp(10e8 + 10e5), + PasswordProtected: true, + CTime: timestamp(10e8), + Token: "token-123", + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventLinkAccessed{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "sharing-userid", "2001-09-09T01:46:40Z", "link 'shareid' was accessed. Success: true", "public_link_accessed") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "itemid-1", "sharing-userid", "shareid") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) // not implemented atm + require.Equal(t, "token-123", ev.ShareToken) + require.Equal(t, true, ev.Success) + }, + }, { + Alias: "Link accessed - failure", + SystemEvent: events.LinkAccessFailed{ + ShareID: linkID("shareid"), + Token: "token-123", + Status: 8, + Message: "access denied", + }, + CheckAuditEvent: func(t *testing.T, b []byte) { + ev := types.AuditEventLinkAccessed{} + require.NoError(t, json.Unmarshal(b, &ev)) + + // AuditEvent fields + checkBaseAuditEvent(t, ev.AuditEvent, "", "", "link 'shareid' was accessed. Success: false", "public_link_accessed") + // AuditEventSharing fields + checkSharingAuditEvent(t, ev.AuditEventSharing, "", "", "shareid") + // AuditEventShareUpdated fields + require.Equal(t, "", ev.ItemType) // not implemented atm + require.Equal(t, "token-123", ev.ShareToken) + require.Equal(t, false, ev.Success) }, }, } @@ -73,8 +313,103 @@ func TestAuditLogging(t *testing.T) { outch <- b }) - for _, tc := range testCases { - inch <- tc.SystemEvent - tc.CheckAuditEvent(t, <-outch) + for i := range testCases { + tc := testCases[i] + t.Run(tc.Alias, func(t *testing.T) { + inch <- tc.SystemEvent + tc.CheckAuditEvent(t, <-outch) + }) } } + +func checkBaseAuditEvent(t *testing.T, ev types.AuditEvent, user string, time string, message string, action string) { + require.Equal(t, "", ev.RemoteAddr) // not implemented atm + require.Equal(t, user, ev.User) + require.Equal(t, "", ev.URL) // not implemented atm + require.Equal(t, "", ev.Method) // not implemented atm + require.Equal(t, "", ev.UserAgent) // not implemented atm + require.Equal(t, time, ev.Time) + require.Equal(t, "admin_audit", ev.App) + require.Equal(t, message, ev.Message) + require.Equal(t, action, ev.Action) + require.Equal(t, false, ev.CLI) // not implemented atm + require.Equal(t, 1, ev.Level) +} + +func checkSharingAuditEvent(t *testing.T, ev types.AuditEventSharing, itemID string, owner string, shareID string) { + require.Equal(t, itemID, ev.FileID) + require.Equal(t, owner, ev.Owner) + require.Equal(t, "", ev.Path) // not implemented atm + require.Equal(t, shareID, ev.ShareID) +} + +func shareID(id string) *collaboration.ShareId { + return &collaboration.ShareId{ + OpaqueId: id, + } +} + +func linkID(id string) *link.PublicShareId { + return &link.PublicShareId{ + OpaqueId: id, + } +} + +func userID(id string) *user.UserId { + return &user.UserId{ + OpaqueId: id, + Idp: "idp", + } +} + +func groupID(id string) *group.GroupId { + return &group.GroupId{ + OpaqueId: id, + Idp: "idp", + } +} + +func resourceID(sid, oid string) *provider.ResourceId { + return &provider.ResourceId{ + StorageId: sid, + OpaqueId: oid, + } +} + +func timestamp(seconds uint64) *rtypes.Timestamp { + return &rtypes.Timestamp{ + Seconds: seconds, + Nanos: 0, + } +} + +func sharePermissions(perms ...string) *collaboration.SharePermissions { + return &collaboration.SharePermissions{ + Permissions: permissions(perms...), + } +} + +func linkPermissions(perms ...string) *link.PublicSharePermissions { + return &link.PublicSharePermissions{ + Permissions: permissions(perms...), + } +} +func permissions(permissions ...string) *provider.ResourcePermissions { + perms := &provider.ResourcePermissions{} + + for _, p := range permissions { + switch p { + case "stat": + perms.Stat = true + case "get_path": + perms.GetPath = true + case "list_container": + perms.ListContainer = true + case "get_quota": + perms.GetQuota = true + + } + } + + return perms +} diff --git a/audit/pkg/types/constants.go b/audit/pkg/types/constants.go new file mode 100644 index 000000000..663996241 --- /dev/null +++ b/audit/pkg/types/constants.go @@ -0,0 +1,61 @@ +package types + +import "fmt" + +// short identifiers for audit actions +const ( + ActionShareCreated = "file_shared" + ActionSharePermissionUpdated = "share_permission_updated" + ActionShareDisplayNameUpdated = "share_name_updated" + ActionSharePasswordUpdated = "share_password_updated" + ActionShareExpirationUpdated = "share_expiration_updated" + ActionShareRemoved = "file_unshared" + ActionShareAccepted = "share_accepted" + ActionShareDeclined = "share_declined" + ActionLinkAccessed = "public_link_accessed" +) + +// MessageShareCreated returns the human readable string that describes the action +func MessageShareCreated(sharer, item, grantee string) string { + return fmt.Sprintf("user '%s' shared file '%s' with '%s'", sharer, item, grantee) +} + +// MessageLinkCreated returns the human readable string that describes the action +func MessageLinkCreated(sharer, item, shareid string) string { + return fmt.Sprintf("user '%s' created a public to file '%s' with id '%s'", sharer, item, shareid) +} + +// MessageShareUpdated returns the human readable string that describes the action +func MessageShareUpdated(sharer, shareID, fieldUpdated string) string { + return fmt.Sprintf("user '%s' updated field '%s' of share '%s'", sharer, fieldUpdated, shareID) +} + +// MessageLinkUpdated returns the human readable string that describes the action +func MessageLinkUpdated(sharer, shareid, fieldUpdated string) string { + return fmt.Sprintf("user '%s' updated field '%s' of public link '%s'", sharer, fieldUpdated, shareid) +} + +// MessageShareRemoved returns the human readable string that describes the action +func MessageShareRemoved(sharer, shareid, itemid string) string { + return fmt.Sprintf("share id:'%s' uid:'%s' item-id:'%s' was removed", shareid, sharer, itemid) +} + +// MessageLinkRemoved returns the human readable string that describes the action +func MessageLinkRemoved(shareid string) string { + return fmt.Sprintf("public link id:'%s' was removed", shareid) +} + +// MessageShareAccepted returns the human readable string that describes the action +func MessageShareAccepted(userid, shareid, sharerid string) string { + return fmt.Sprintf("user '%s' accepted share '%s' from user '%s'", userid, shareid, sharerid) +} + +// MessageShareDeclined returns the human readable string that describes the action +func MessageShareDeclined(userid, shareid, sharerid string) string { + return fmt.Sprintf("user '%s' declined share '%s' from user '%s'", userid, shareid, sharerid) +} + +// MessageLinkAccessed returns the human readable string that describes the action +func MessageLinkAccessed(linkid string, success bool) string { + return fmt.Sprintf("link '%s' was accessed. Success: %v", linkid, success) +} diff --git a/audit/pkg/types/conversion.go b/audit/pkg/types/conversion.go index c4ab66f5e..c90ab0eec 100644 --- a/audit/pkg/types/conversion.go +++ b/audit/pkg/types/conversion.go @@ -5,16 +5,10 @@ import ( "time" "github.com/cs3org/reva/v2/pkg/events" -) -// actions -const ( - actionShareCreated = "file_shared" -) - -// messages -const ( - messageShareCreated = "user '%s' shared file '%s' with '%s'" + group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) // BasicAuditEvent creates an AuditEvent from given values @@ -37,39 +31,30 @@ func BasicAuditEvent(uid string, ctime string, msg string, action string) AuditE } // SharingAuditEvent creates an AuditEventSharing from given values -func SharingAuditEvent(fileid string, uid string, base AuditEvent) AuditEventSharing { +func SharingAuditEvent(shareid string, fileid string, uid string, base AuditEvent) AuditEventSharing { return AuditEventSharing{ AuditEvent: base, FileID: fileid, Owner: uid, + ShareID: shareid, // NOTE: those values are not in the events and can therefore not be filled at the moment - ShareID: "", - Path: "", + Path: "", } } // ShareCreated converts a ShareCreated Event to an AuditEventShareCreated func ShareCreated(ev events.ShareCreated) AuditEventShareCreated { - with := "" - typ := "" - if ev.GranteeUserID != nil && ev.GranteeUserID.OpaqueId != "" { - with = ev.GranteeUserID.OpaqueId - typ = "user" - } else if ev.GranteeGroupID != nil && ev.GranteeGroupID.OpaqueId != "" { - with = ev.GranteeGroupID.OpaqueId - typ = "group" - } uid := ev.Sharer.OpaqueId - t := time.Unix(int64(ev.CTime.Seconds), int64(ev.CTime.Nanos)).Format(time.RFC3339) - base := BasicAuditEvent(uid, t, fmt.Sprintf(messageShareCreated, uid, ev.ItemID.OpaqueId, with), actionShareCreated) + with, typ := extractGrantee(ev.GranteeUserID, ev.GranteeGroupID) + base := BasicAuditEvent(uid, formatTime(ev.CTime), MessageShareCreated(uid, ev.ItemID.OpaqueId, with), ActionShareCreated) return AuditEventShareCreated{ - AuditEventSharing: SharingAuditEvent(ev.ItemID.OpaqueId, uid, base), + AuditEventSharing: SharingAuditEvent("", ev.ItemID.OpaqueId, uid, base), ShareOwner: uid, ShareWith: with, ShareType: typ, - // NOTE: those values are not in the events and can therefore not be filled at the moment + // NOTE: those values are not in the event and can therefore not be filled at the moment ItemType: "", ExpirationDate: "", SharePass: false, @@ -77,3 +62,198 @@ func ShareCreated(ev events.ShareCreated) AuditEventShareCreated { ShareToken: "", } } + +// LinkCreated converts a ShareCreated Event to an AuditEventShareCreated +func LinkCreated(ev events.LinkCreated) AuditEventShareCreated { + uid := ev.Sharer.OpaqueId + with, typ := "", "link" + base := BasicAuditEvent(uid, formatTime(ev.CTime), MessageLinkCreated(uid, ev.ItemID.OpaqueId, ev.ShareID.OpaqueId), ActionShareCreated) + return AuditEventShareCreated{ + AuditEventSharing: SharingAuditEvent("", ev.ItemID.OpaqueId, uid, base), + ShareOwner: uid, + ShareWith: with, + ShareType: typ, + ExpirationDate: formatTime(ev.Expiration), + SharePass: ev.PasswordProtected, + Permissions: ev.Permissions.String(), + ShareToken: ev.Token, + + // NOTE: those values are not in the event and can therefore not be filled at the moment + ItemType: "", + } +} + +// ShareUpdated converts a ShareUpdated event to an AuditEventShareUpdated +func ShareUpdated(ev events.ShareUpdated) AuditEventShareUpdated { + uid := ev.Sharer.OpaqueId + with, typ := extractGrantee(ev.GranteeUserID, ev.GranteeGroupID) + base := BasicAuditEvent(uid, formatTime(ev.MTime), MessageShareUpdated(uid, ev.ShareID.OpaqueId, ev.Updated), updateType(ev.Updated)) + return AuditEventShareUpdated{ + AuditEventSharing: SharingAuditEvent(ev.ShareID.GetOpaqueId(), ev.ItemID.OpaqueId, uid, base), + ShareOwner: uid, + ShareWith: with, + ShareType: typ, + Permissions: ev.Permissions.Permissions.String(), + + // NOTE: those values are not in the event and can therefore not be filled at the moment + ItemType: "", + ExpirationDate: "", + SharePass: false, + ShareToken: "", + } +} + +// LinkUpdated converts a LinkUpdated event to an AuditEventShareUpdated +func LinkUpdated(ev events.LinkUpdated) AuditEventShareUpdated { + uid := ev.Sharer.OpaqueId + with, typ := "", "link" + base := BasicAuditEvent(uid, formatTime(ev.CTime), MessageLinkUpdated(uid, ev.ShareID.OpaqueId, ev.FieldUpdated), updateType(ev.FieldUpdated)) + return AuditEventShareUpdated{ + AuditEventSharing: SharingAuditEvent(ev.ShareID.GetOpaqueId(), ev.ItemID.OpaqueId, uid, base), + ShareOwner: uid, + ShareWith: with, + ShareType: typ, + Permissions: ev.Permissions.Permissions.String(), + ExpirationDate: formatTime(ev.Expiration), + SharePass: ev.PasswordProtected, + ShareToken: ev.Token, + + // NOTE: those values are not in the event and can therefore not be filled at the moment + ItemType: "", + } +} + +// ShareRemoved converts a ShareRemoved event to an AuditEventShareRemoved +func ShareRemoved(ev events.ShareRemoved) AuditEventShareRemoved { + sid, uid, iid, with, typ := "", "", "", "", "" + if ev.ShareID != nil { + sid = ev.ShareID.GetOpaqueId() + } + + if ev.ShareKey != nil { + uid = ev.ShareKey.GetOwner().GetOpaqueId() + iid = ev.ShareKey.GetResourceId().GetOpaqueId() + with, typ = extractGrantee(ev.ShareKey.GetGrantee().GetUserId(), ev.ShareKey.GetGrantee().GetGroupId()) + } + base := BasicAuditEvent(uid, "", MessageShareRemoved(uid, sid, iid), ActionShareRemoved) + return AuditEventShareRemoved{ + AuditEventSharing: SharingAuditEvent(sid, iid, uid, base), + ShareWith: with, + ShareType: typ, + + // NOTE: those values are not in the event and can therefore not be filled at the moment + ItemType: "", + } +} + +// LinkRemoved converts a LinkRemoved event to an AuditEventShareRemoved +func LinkRemoved(ev events.LinkRemoved) AuditEventShareRemoved { + uid, sid, typ := "", "", "link" + if ev.ShareID != nil { + sid = ev.ShareID.GetOpaqueId() + } else { + sid = ev.ShareToken + } + + base := BasicAuditEvent(uid, "", MessageLinkRemoved(sid), ActionShareRemoved) + return AuditEventShareRemoved{ + AuditEventSharing: SharingAuditEvent(sid, "", uid, base), + ShareWith: "", + ShareType: typ, + + // NOTE: those values are not in the event and can therefore not be filled at the moment + ItemType: "", + } +} + +// ReceivedShareUpdated converts a ReceivedShareUpdated event to an AuditEventReceivedShareUpdated +func ReceivedShareUpdated(ev events.ReceivedShareUpdated) AuditEventReceivedShareUpdated { + uid := ev.Sharer.GetOpaqueId() + sid := ev.ShareID.GetOpaqueId() + with, typ := extractGrantee(ev.GranteeUserID, ev.GranteeGroupID) + itemID := ev.ItemID.GetOpaqueId() + + msg, utype := "", "" + switch ev.State { + case "SHARE_STATE_ACCEPTED": + msg = MessageShareAccepted(with, sid, uid) + utype = ActionShareAccepted + case "SHARE_STATE_DECLINED": + msg = MessageShareDeclined(with, sid, uid) + utype = ActionShareDeclined + } + base := BasicAuditEvent(with, formatTime(ev.MTime), msg, utype) + return AuditEventReceivedShareUpdated{ + AuditEventSharing: SharingAuditEvent(sid, itemID, uid, base), + ShareType: typ, + ShareWith: with, + + // NOTE: those values are not in the event and can therefore not be filled at the moment + ItemType: "", + } +} + +// LinkAccessed converts a LinkAccessed event to an AuditEventLinkAccessed +func LinkAccessed(ev events.LinkAccessed) AuditEventLinkAccessed { + uid := ev.Sharer.OpaqueId + base := BasicAuditEvent(uid, formatTime(ev.CTime), MessageLinkAccessed(ev.ShareID.GetOpaqueId(), true), ActionLinkAccessed) + return AuditEventLinkAccessed{ + AuditEventSharing: SharingAuditEvent(ev.ShareID.GetOpaqueId(), ev.ItemID.OpaqueId, uid, base), + ShareToken: ev.Token, + Success: true, + + // NOTE: those values are not in the event and can therefore not be filled at the moment + ItemType: "", + } +} + +// LinkAccessFailed converts a LinkAccessFailed event to an AuditEventLinkAccessed +func LinkAccessFailed(ev events.LinkAccessFailed) AuditEventLinkAccessed { + base := BasicAuditEvent("", "", MessageLinkAccessed(ev.ShareID.GetOpaqueId(), false), ActionLinkAccessed) + return AuditEventLinkAccessed{ + AuditEventSharing: SharingAuditEvent(ev.ShareID.GetOpaqueId(), "", "", base), + ShareToken: ev.Token, + Success: false, + + // NOTE: those values are not in the event and can therefore not be filled at the moment + ItemType: "", + } +} + +func extractGrantee(uid *user.UserId, gid *group.GroupId) (string, string) { + switch { + case uid != nil && uid.OpaqueId != "": + return uid.OpaqueId, "user" + case gid != nil && gid.OpaqueId != "": + return gid.OpaqueId, "group" + } + + return "", "" +} + +func formatTime(t *types.Timestamp) string { + if t == nil { + return "" + } + return time.Unix(int64(t.Seconds), int64(t.Nanos)).UTC().Format(time.RFC3339) +} + +func updateType(u string) string { + switch { + case u == "permissions": + return ActionSharePermissionUpdated + case u == "displayname": + return ActionShareDisplayNameUpdated + case u == "TYPE_PERMISSIONS": + return ActionSharePermissionUpdated + case u == "TYPE_DISPLAYNAME": + return ActionShareDisplayNameUpdated + case u == "TYPE_PASSWORD": + return ActionSharePasswordUpdated + case u == "TYPE_EXPIRATION": + return ActionShareExpirationUpdated + default: + fmt.Println("Unknown update type", u) + return "" + } +} diff --git a/audit/pkg/types/events.go b/audit/pkg/types/events.go index 7a9825ef0..801dd4179 100644 --- a/audit/pkg/types/events.go +++ b/audit/pkg/types/events.go @@ -8,5 +8,13 @@ import ( func RegisteredEvents() []events.Unmarshaller { return []events.Unmarshaller{ events.ShareCreated{}, + events.ShareUpdated{}, + events.LinkCreated{}, + events.LinkUpdated{}, + events.ShareRemoved{}, + events.LinkRemoved{}, + events.ReceivedShareUpdated{}, + events.LinkAccessed{}, + events.LinkAccessFailed{}, } } diff --git a/audit/pkg/types/types.go b/audit/pkg/types/types.go index cd07e994f..17e2d0a3e 100644 --- a/audit/pkg/types/types.go +++ b/audit/pkg/types/types.go @@ -38,3 +38,41 @@ type AuditEventShareCreated struct { ShareOwner string // The UID of the share owner. ShareToken string // For link shares the unique token, else null } + +// AuditEventShareUpdated is the event logged when a share is updated +type AuditEventShareUpdated struct { + AuditEventSharing + + ItemType string // file or folder + ExpirationDate string // The text expiration date in format 'yyyy-mm-dd' + SharePass bool // If the share is password protected. + Permissions string // The permissions string eg: "READ" + ShareType string // group user or link + ShareWith string // The UID or GID of the share recipient. (not available for public link) + ShareOwner string // The UID of the share owner. + ShareToken string // For link shares the unique token, else null +} + +// AuditEventShareRemoved is the event logged when a share is removed +type AuditEventShareRemoved struct { + AuditEventSharing + ItemType string // file or folder + ShareType string // group user or link + ShareWith string // The UID or GID of the share recipient. +} + +// AuditEventReceivedShareUpdated is the event logged when a share is accepted or declined +type AuditEventReceivedShareUpdated struct { + AuditEventSharing + ItemType string // file or folder + ShareType string // group user or link + ShareWith string // The UID or GID of the share recipient. +} + +// AuditEventLinkAccessed is the event logged when a link is accessed +type AuditEventLinkAccessed struct { + AuditEventSharing + ShareToken string // The share token. + Success bool // If the request was successful. + ItemType string // file or folder +} diff --git a/changelog/unreleased/sharing-audit-events.md b/changelog/unreleased/sharing-audit-events.md new file mode 100644 index 000000000..5065e2d83 --- /dev/null +++ b/changelog/unreleased/sharing-audit-events.md @@ -0,0 +1,5 @@ +Enhancement: log sharing events in audit service + +Contains sharing related events. See full list in audit/pkg/types/events.go + +https://github.com/owncloud/ocis/pull/3301 diff --git a/go.mod b/go.mod index 829dead3e..c07f3109e 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/blevesearch/bleve/v2 v2.3.1 github.com/coreos/go-oidc/v3 v3.1.0 github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19 - github.com/cs3org/reva/v2 v2.0.0-20220304131900-b8be80d1ba81 + github.com/cs3org/reva/v2 v2.0.0-20220314085001-8e5b22a20a3f github.com/disintegration/imaging v1.6.2 github.com/glauth/glauth/v2 v2.0.0-20211021011345-ef3151c28733 github.com/go-chi/chi/v5 v5.0.7 diff --git a/go.sum b/go.sum index ac07e635c..a4f60af13 100644 --- a/go.sum +++ b/go.sum @@ -342,8 +342,8 @@ github.com/crewjam/saml v0.4.5/go.mod h1:qCJQpUtZte9R1ZjUBcW8qtCNlinbO363ooNl02S github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19 h1:1jqPH58jCxvbaJ9WLIJ7W2/m622bWS6ChptzljSG6IQ= github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= -github.com/cs3org/reva/v2 v2.0.0-20220304131900-b8be80d1ba81 h1:g6c1HYGTSpDnf6uNPXYIOySVk0P545zWUPmdPWEcMps= -github.com/cs3org/reva/v2 v2.0.0-20220304131900-b8be80d1ba81/go.mod h1:XNtK1HEClNzmz5vyQa2DUw4KH3oqBjQoEsV1LhAGlV0= +github.com/cs3org/reva/v2 v2.0.0-20220314085001-8e5b22a20a3f h1:tv7v6OjbFoDFNB2ikGC+LLaWEOIAJnrZjyO5LRTDL0g= +github.com/cs3org/reva/v2 v2.0.0-20220314085001-8e5b22a20a3f/go.mod h1:XNtK1HEClNzmz5vyQa2DUw4KH3oqBjQoEsV1LhAGlV0= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=