diff --git a/audit/pkg/service/service.go b/audit/pkg/service/service.go index f2f09766a01..adf16f29146 100644 --- a/audit/pkg/service/service.go +++ b/audit/pkg/service/service.go @@ -85,6 +85,20 @@ func StartAuditLogger(ctx context.Context, ch <-chan interface{}, log log.Logger auditEvent = types.SpaceEnabled(ev) case events.SpaceDeleted: auditEvent = types.SpaceDeleted(ev) + case events.UserCreated: + auditEvent = types.UserCreated(ev) + case events.UserDeleted: + auditEvent = types.UserDeleted(ev) + case events.UserFeatureChanged: + auditEvent = types.UserFeatureChanged(ev) + case events.GroupCreated: + auditEvent = types.GroupCreated(ev) + case events.GroupDeleted: + auditEvent = types.GroupDeleted(ev) + case events.GroupMemberAdded: + auditEvent = types.GroupMemberAdded(ev) + case events.GroupMemberRemoved: + auditEvent = types.GroupMemberRemoved(ev) default: log.Error().Interface("event", ev).Msg(fmt.Sprintf("can't handle event of type '%T'", ev)) continue diff --git a/audit/pkg/types/constants.go b/audit/pkg/types/constants.go index 4f2f79d5e21..afd426491c5 100644 --- a/audit/pkg/types/constants.go +++ b/audit/pkg/types/constants.go @@ -30,6 +30,17 @@ const ( ActionSpaceDisabled = "space_disabled" ActionSpaceEnabled = "space_enabled" ActionSpaceDeleted = "space_deleted" + + // Users + ActionUserCreated = "user_created" + ActionUserDeleted = "user_deleted" + ActionUserFeatureChanged = "user_feature_changed" + + // Groups + ActionGroupCreated = "group_created" + ActionGroupDeleted = "group_deleted" + ActionGroupMemberAdded = "group_member_added" + ActionGroupMemberRemoved = "group_member_removed" ) // MessageShareCreated returns the human readable string that describes the action @@ -136,3 +147,38 @@ func MessageSpaceEnabled(spaceID string) string { func MessageSpaceDeleted(spaceID string) string { return fmt.Sprintf("Space '%s' was deleted", spaceID) } + +// MessageUserCreated returns the human readable string that describes the action +func MessageUserCreated(userID string) string { + return fmt.Sprintf("User '%s' was created", userID) +} + +// MessageUserDeleted returns the human readable string that describes the action +func MessageUserDeleted(userID string) string { + return fmt.Sprintf("User '%s' was deleted", userID) +} + +// MessageUserFeatureChanged returns the human readable string that describes the action +func MessageUserFeatureChanged(userID, feature, value string) string { + return fmt.Sprintf("User '%s's' feature '%s' was changed to '%s'", userID, feature, value) +} + +// MessageGroupCreated returns the human readable string that describes the action +func MessageGroupCreated(groupID string) string { + return fmt.Sprintf("Group '%s' was created", groupID) +} + +// MessageGroupDeleted returns the human readable string that describes the action +func MessageGroupDeleted(groupID string) string { + return fmt.Sprintf("Group '%s' was deleted", groupID) +} + +// MessageGroupMemberAdded returns the human readable string that describes the action +func MessageGroupMemberAdded(userID, groupID string) string { + return fmt.Sprintf("User '%s' was added to group '%s'", userID, groupID) +} + +// MessageGroupMemberRemoved returns the human readable string that describes the action +func MessageGroupMemberRemoved(userID, groupID string) string { + return fmt.Sprintf("User '%s' was removed from group '%s'", userID, groupID) +} diff --git a/audit/pkg/types/conversion.go b/audit/pkg/types/conversion.go index f03732daf01..6413bee6ddd 100644 --- a/audit/pkg/types/conversion.go +++ b/audit/pkg/types/conversion.go @@ -369,6 +369,76 @@ func SpaceDeleted(ev events.SpaceDeleted) AuditEventSpaceDeleted { } } +// UserCreated converts a UserCreated event to an AuditEventUserCreated +func UserCreated(ev events.UserCreated) AuditEventUserCreated { + base := BasicAuditEvent("", "", MessageUserCreated(ev.UserID), ActionUserCreated) + return AuditEventUserCreated{ + AuditEvent: base, + UserID: ev.UserID, + } +} + +// UserDeleted converts a UserCreated event to an AuditEventUserDeleted +func UserDeleted(ev events.UserDeleted) AuditEventUserDeleted { + base := BasicAuditEvent("", "", MessageUserDeleted(ev.UserID), ActionUserDeleted) + return AuditEventUserDeleted{ + AuditEvent: base, + UserID: ev.UserID, + } +} + +// UserFeatureChanged converts a UserFeatureChanged event to an AuditEventUserFeatureChanged +func UserFeatureChanged(ev events.UserFeatureChanged) AuditEventUserFeatureChanged { + msg := MessageUserFeatureChanged(ev.UserID, ev.Feature, ev.Value) + base := BasicAuditEvent("", "", msg, ActionUserFeatureChanged) + return AuditEventUserFeatureChanged{ + AuditEvent: base, + UserID: ev.UserID, + Feature: ev.Feature, + Value: ev.Value, + } +} + +// GroupCreated converts a GroupCreated event to an AuditEventGroupCreated +func GroupCreated(ev events.GroupCreated) AuditEventGroupCreated { + base := BasicAuditEvent("", "", MessageGroupCreated(ev.GroupID), ActionGroupCreated) + return AuditEventGroupCreated{ + AuditEvent: base, + GroupID: ev.GroupID, + } +} + +// GroupDeleted converts a GroupDeleted event to an AuditEventGroupDeleted +func GroupDeleted(ev events.GroupDeleted) AuditEventGroupDeleted { + base := BasicAuditEvent("", "", MessageGroupDeleted(ev.GroupID), ActionGroupDeleted) + return AuditEventGroupDeleted{ + AuditEvent: base, + GroupID: ev.GroupID, + } +} + +// GroupMemberAdded converts a GroupMemberAdded event to an AuditEventGroupMemberAdded +func GroupMemberAdded(ev events.GroupMemberAdded) AuditEventGroupMemberAdded { + msg := MessageGroupMemberAdded(ev.GroupID, ev.UserID) + base := BasicAuditEvent("", "", msg, ActionGroupMemberAdded) + return AuditEventGroupMemberAdded{ + AuditEvent: base, + GroupID: ev.GroupID, + UserID: ev.UserID, + } +} + +// GroupMemberRemoved converts a GroupMemberRemoved event to an AuditEventGroupMemberRemove +func GroupMemberRemoved(ev events.GroupMemberRemoved) AuditEventGroupMemberRemoved { + msg := MessageGroupMemberRemoved(ev.GroupID, ev.UserID) + base := BasicAuditEvent("", "", msg, ActionGroupMemberRemoved) + return AuditEventGroupMemberRemoved{ + AuditEvent: base, + GroupID: ev.GroupID, + UserID: ev.UserID, + } +} + func extractGrantee(uid *user.UserId, gid *group.GroupId) (string, string) { switch { case uid != nil && uid.OpaqueId != "": diff --git a/audit/pkg/types/events.go b/audit/pkg/types/events.go index 743044a049f..1862b1f8070 100644 --- a/audit/pkg/types/events.go +++ b/audit/pkg/types/events.go @@ -28,5 +28,12 @@ func RegisteredEvents() []events.Unmarshaller { events.SpaceEnabled{}, events.SpaceDisabled{}, events.SpaceDeleted{}, + events.UserCreated{}, + events.UserDeleted{}, + events.UserFeatureChanged{}, + events.GroupCreated{}, + events.GroupDeleted{}, + events.GroupMemberAdded{}, + events.GroupMemberRemoved{}, } } diff --git a/audit/pkg/types/types.go b/audit/pkg/types/types.go index d4227a196be..78baca3b9f5 100644 --- a/audit/pkg/types/types.go +++ b/audit/pkg/types/types.go @@ -197,3 +197,49 @@ type AuditEventSpaceEnabled struct { type AuditEventSpaceDeleted struct { AuditEventSpaces } + +// AuditEventUserCreated is the event logged when a user is created +type AuditEventUserCreated struct { + AuditEvent + UserID string +} + +// AuditEventUserDeleted is the event logged when a user is deleted +type AuditEventUserDeleted struct { + AuditEvent + UserID string +} + +// AuditEventUserFeatureChanged is the event logged when a user feature is changed +type AuditEventUserFeatureChanged struct { + AuditEvent + UserID string + Feature string + Value string +} + +// AuditEventGroupCreated is the event logged when a group is created +type AuditEventGroupCreated struct { + AuditEvent + GroupID string +} + +// AuditEventGroupDeleted is the event logged when a group is deleted +type AuditEventGroupDeleted struct { + AuditEvent + GroupID string +} + +// AuditEventGroupMemberAdded is the event logged when a group member is added +type AuditEventGroupMemberAdded struct { + AuditEvent + GroupID string + UserID string +} + +// AuditEventGroupMemberRemoved is the event logged when a group member is removed +type AuditEventGroupMemberRemoved struct { + AuditEvent + GroupID string + UserID string +} diff --git a/changelog/unreleased/user-group-audit.md b/changelog/unreleased/user-group-audit.md new file mode 100644 index 00000000000..ff3f6310a46 --- /dev/null +++ b/changelog/unreleased/user-group-audit.md @@ -0,0 +1,12 @@ +Enhancement: Implement audit events for user and groups + +Added audit events for users and groups. This will log: +* User creation +* User deletion +* User property change (currently only email) +* Group creation +* Group deletion +* Group member add +* Group member remove + +https://github.com/owncloud/ocis/pull/3467 diff --git a/go.mod b/go.mod index 6df2c382211..30395142600 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/blevesearch/bleve/v2 v2.3.2 github.com/coreos/go-oidc/v3 v3.1.0 github.com/cs3org/go-cs3apis v0.0.0-20220328105952-297bef33e13f - github.com/cs3org/reva/v2 v2.0.0-20220404075659-19fd0b28297b + github.com/cs3org/reva/v2 v2.0.0-20220406120423-c7a9ff164c46 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 c0e42ce0bf0..c2dd0a1004b 100644 --- a/go.sum +++ b/go.sum @@ -337,8 +337,8 @@ github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD9 github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= github.com/cs3org/go-cs3apis v0.0.0-20220328105952-297bef33e13f h1:emnlOWc1s2gx77MViLnZH9yh5TRHKsykRu6rJjx3lkM= github.com/cs3org/go-cs3apis v0.0.0-20220328105952-297bef33e13f/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= -github.com/cs3org/reva/v2 v2.0.0-20220404075659-19fd0b28297b h1:CqHYID4t286wle5kXcFfUtxxw6Vz0XlbGCiB/Z8rDbI= -github.com/cs3org/reva/v2 v2.0.0-20220404075659-19fd0b28297b/go.mod h1:1siLO6MV57uSyzQxPbfM6qNA9NP6aagN3/yKOE/FwtM= +github.com/cs3org/reva/v2 v2.0.0-20220406120423-c7a9ff164c46 h1:OB0Y1r4T8OvcLT1apVvuc4Ne8t6HD2nGFzgpfGTAVcg= +github.com/cs3org/reva/v2 v2.0.0-20220406120423-c7a9ff164c46/go.mod h1:1siLO6MV57uSyzQxPbfM6qNA9NP6aagN3/yKOE/FwtM= 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= diff --git a/graph/Makefile b/graph/Makefile index 88ecd177d8f..682b7f725ae 100644 --- a/graph/Makefile +++ b/graph/Makefile @@ -27,6 +27,8 @@ include ../.make/generate.mk ci-go-generate: $(MOCKERY) # CI runs ci-node-generate automatically before this target $(MOCKERY) --dir pkg/service/v0 --case underscore --name GatewayClient $(MOCKERY) --dir pkg/service/v0 --case underscore --name HTTPClient + $(MOCKERY) --dir pkg/service/v0 --case underscore --name Publisher + .PHONY: ci-node-generate ci-node-generate: diff --git a/graph/mocks/publisher.go b/graph/mocks/publisher.go new file mode 100644 index 00000000000..88c802e6d31 --- /dev/null +++ b/graph/mocks/publisher.go @@ -0,0 +1,34 @@ +// Code generated by mockery v2.9.4. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + events "go-micro.dev/v4/events" +) + +// Publisher is an autogenerated mock type for the Publisher type +type Publisher struct { + mock.Mock +} + +// Publish provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Publisher) Publish(_a0 string, _a1 interface{}, _a2 ...events.PublishOption) error { + _va := make([]interface{}, len(_a2)) + for _i := range _a2 { + _va[_i] = _a2[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0, _a1) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, interface{}, ...events.PublishOption) error); ok { + r0 = rf(_a0, _a1, _a2...) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/graph/pkg/config/config.go b/graph/pkg/config/config.go index 3501a30a032..fe59ea6901d 100644 --- a/graph/pkg/config/config.go +++ b/graph/pkg/config/config.go @@ -23,6 +23,7 @@ type Config struct { Spaces Spaces `yaml:"spaces"` Identity Identity `yaml:"identity"` + Events Events `yaml:"events"` Context context.Context `yaml:"-"` } @@ -62,3 +63,9 @@ type Identity struct { Backend string `yaml:"backend" env:"GRAPH_IDENTITY_BACKEND"` LDAP LDAP `yaml:"ldap"` } + +// Events combines the configuration options for the event bus. +type Events struct { + Endpoint string `yaml:"events_endpoint" env:"GRAPH_EVENTS_ENDPOINT" desc:"the address of the streaming service"` + Cluster string `yaml:"events_cluster" env:"GRAPH_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` +} diff --git a/graph/pkg/config/defaults/defaultconfig.go b/graph/pkg/config/defaults/defaultconfig.go index 9c2eba50cb8..5bab47e892b 100644 --- a/graph/pkg/config/defaults/defaultconfig.go +++ b/graph/pkg/config/defaults/defaultconfig.go @@ -57,6 +57,10 @@ func DefaultConfig() *config.Config { GroupIDAttribute: "owncloudUUID", }, }, + Events: config.Events{ + Endpoint: "127.0.0.1:9233", + Cluster: "ocis-cluster", + }, } } diff --git a/graph/pkg/server/http/server.go b/graph/pkg/server/http/server.go index 36f64ef3ff1..0456e1bcb9a 100644 --- a/graph/pkg/server/http/server.go +++ b/graph/pkg/server/http/server.go @@ -1,6 +1,8 @@ package http import ( + "github.com/asim/go-micro/plugins/events/natsjs/v4" + "github.com/cs3org/reva/v2/pkg/events/server" chimiddleware "github.com/go-chi/chi/v5/middleware" graphMiddleware "github.com/owncloud/ocis/graph/pkg/middleware" svc "github.com/owncloud/ocis/graph/pkg/service/v0" @@ -8,6 +10,7 @@ import ( "github.com/owncloud/ocis/ocis-pkg/middleware" "github.com/owncloud/ocis/ocis-pkg/service/http" "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/pkg/errors" "go-micro.dev/v4" ) @@ -25,6 +28,17 @@ func Server(opts ...Option) (http.Service, error) { http.Flags(options.Flags...), ) + publisher, err := server.NewNatsStream( + natsjs.Address(options.Config.Events.Endpoint), + natsjs.ClusterID(options.Config.Events.Cluster), + ) + if err != nil { + options.Logger.Error(). + Err(err). + Msg("Error initializing events publisher") + return http.Service{}, errors.Wrap(err, "could not initialize events publisher") + } + handle := svc.NewService( svc.Logger(options.Logger), svc.Config(options.Config), @@ -42,6 +56,7 @@ func Server(opts ...Option) (http.Service, error) { account.JWTSecret(options.Config.TokenManager.JWTSecret), ), ), + svc.EventsPublisher(publisher), ) { diff --git a/graph/pkg/service/v0/graph.go b/graph/pkg/service/v0/graph.go index 2dd618d09ae..3d2ef13b895 100644 --- a/graph/pkg/service/v0/graph.go +++ b/graph/pkg/service/v0/graph.go @@ -7,11 +7,13 @@ import ( "github.com/ReneKroon/ttlcache/v2" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/events" "github.com/go-chi/chi/v5" "github.com/owncloud/ocis/graph/pkg/config" "github.com/owncloud/ocis/graph/pkg/identity" "github.com/owncloud/ocis/ocis-pkg/log" settingssvc "github.com/owncloud/ocis/protogen/gen/ocis/services/settings/v0" + mevents "go-micro.dev/v4/events" "google.golang.org/grpc" ) @@ -51,6 +53,11 @@ type GatewayClient interface { GetQuota(ctx context.Context, in *gateway.GetQuotaRequest, opts ...grpc.CallOption) (*provider.GetQuotaResponse, error) } +// Publisher is the interface for events publisher +type Publisher interface { + Publish(string, interface{}, ...mevents.PublishOption) error +} + // HTTPClient is the subset of the http.Client that is being used to interact with the download gateway type HTTPClient interface { Do(req *http.Request) (*http.Response, error) @@ -69,6 +76,7 @@ type Graph struct { httpClient HTTPClient roleService settingssvc.RoleService spacePropertiesCache *ttlcache.Cache + eventsPublisher events.Publisher } // ServeHTTP implements the Service interface. @@ -86,6 +94,14 @@ func (g Graph) GetHTTPClient() HTTPClient { return g.httpClient } +func (g Graph) publishEvent(ev interface{}) { + if err := events.Publish(g.eventsPublisher, ev); err != nil { + g.logger.Error(). + Err(err). + Msg("could not publish user created event") + } +} + type listResponse struct { Value interface{} `json:"value,omitempty"` } diff --git a/graph/pkg/service/v0/graph_test.go b/graph/pkg/service/v0/graph_test.go index bd07a5cde8a..e63ca3d4e9e 100644 --- a/graph/pkg/service/v0/graph_test.go +++ b/graph/pkg/service/v0/graph_test.go @@ -25,20 +25,23 @@ import ( var _ = Describe("Graph", func() { var ( - svc service.Service - gatewayClient *mocks.GatewayClient - httpClient *mocks.HTTPClient - ctx context.Context + svc service.Service + gatewayClient *mocks.GatewayClient + httpClient *mocks.HTTPClient + eventsPublisher mocks.Publisher + ctx context.Context ) JustBeforeEach(func() { ctx = context.Background() gatewayClient = &mocks.GatewayClient{} httpClient = &mocks.HTTPClient{} + eventsPublisher = mocks.Publisher{} svc = service.NewService( service.Config(defaults.DefaultConfig()), service.WithGatewayClient(gatewayClient), service.WithHTTPClient(httpClient), + service.EventsPublisher(&eventsPublisher), ) }) diff --git a/graph/pkg/service/v0/groups.go b/graph/pkg/service/v0/groups.go index 0baa907595c..3124da1edf5 100644 --- a/graph/pkg/service/v0/groups.go +++ b/graph/pkg/service/v0/groups.go @@ -13,6 +13,7 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/graph/pkg/service/v0/errorcode" + "github.com/cs3org/reva/v2/pkg/events" "github.com/go-chi/chi/v5" "github.com/go-chi/render" ) @@ -81,6 +82,7 @@ func (g Graph) PostGroup(w http.ResponseWriter, r *http.Request) { return } + g.publishEvent(events.GroupCreated{GroupID: *grp.Id}) render.Status(r, http.StatusOK) render.JSON(w, r, grp) } @@ -197,6 +199,8 @@ func (g Graph) DeleteGroup(w http.ResponseWriter, r *http.Request) { } return } + + g.publishEvent(events.GroupDeleted{GroupID: groupID}) render.Status(r, http.StatusNoContent) render.NoContent(w, r) } @@ -279,6 +283,8 @@ func (g Graph) PostGroupMember(w http.ResponseWriter, r *http.Request) { } return } + + g.publishEvent(events.GroupMemberAdded{GroupID: groupID, UserID: id}) render.Status(r, http.StatusNoContent) render.NoContent(w, r) } @@ -322,6 +328,7 @@ func (g Graph) DeleteGroupMember(w http.ResponseWriter, r *http.Request) { } return } + g.publishEvent(events.GroupMemberRemoved{GroupID: groupID, UserID: memberID}) render.Status(r, http.StatusNoContent) render.NoContent(w, r) } diff --git a/graph/pkg/service/v0/option.go b/graph/pkg/service/v0/option.go index cd190487744..39c0765ce19 100644 --- a/graph/pkg/service/v0/option.go +++ b/graph/pkg/service/v0/option.go @@ -3,6 +3,7 @@ package svc import ( "net/http" + "github.com/cs3org/reva/v2/pkg/events" "github.com/owncloud/ocis/graph/pkg/config" "github.com/owncloud/ocis/ocis-pkg/log" "github.com/owncloud/ocis/ocis-pkg/roles" @@ -14,13 +15,14 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Config *config.Config - Middleware []func(http.Handler) http.Handler - GatewayClient GatewayClient - HTTPClient HTTPClient - RoleService settingssvc.RoleService - RoleManager *roles.Manager + Logger log.Logger + Config *config.Config + Middleware []func(http.Handler) http.Handler + GatewayClient GatewayClient + HTTPClient HTTPClient + RoleService settingssvc.RoleService + RoleManager *roles.Manager + EventsPublisher events.Publisher } // newOptions initializes the available default options. @@ -82,3 +84,10 @@ func RoleManager(val *roles.Manager) Option { o.RoleManager = val } } + +// EventsPublisher provides a function to set the EventsPublisher option. +func EventsPublisher(val events.Publisher) Option { + return func(o *Options) { + o.EventsPublisher = val + } +} diff --git a/graph/pkg/service/v0/service.go b/graph/pkg/service/v0/service.go index 7ac1d3590c4..7833d4fd665 100644 --- a/graph/pkg/service/v0/service.go +++ b/graph/pkg/service/v0/service.go @@ -96,6 +96,7 @@ func NewService(opts ...Option) Service { logger: &options.Logger, identityBackend: backend, spacePropertiesCache: ttlcache.NewCache(), + eventsPublisher: options.EventsPublisher, } if options.GatewayClient == nil { var err error diff --git a/graph/pkg/service/v0/users.go b/graph/pkg/service/v0/users.go index 0cc29954f96..4de118f954e 100644 --- a/graph/pkg/service/v0/users.go +++ b/graph/pkg/service/v0/users.go @@ -12,6 +12,7 @@ import ( "github.com/CiscoM31/godata" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" "github.com/go-chi/chi/v5" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" @@ -133,6 +134,9 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, fmt.Sprintf("could not assign role to account %s", err.Error())) return } + + g.publishEvent(events.UserCreated{UserID: *u.Id}) + render.Status(r, http.StatusOK) render.JSON(w, r, u) } @@ -187,6 +191,9 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) } } + + g.publishEvent(events.UserDeleted{UserID: userID}) + render.Status(r, http.StatusNoContent) render.NoContent(w, r) } @@ -211,12 +218,18 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { return } + var ( + changedFeature string + newValue string + ) if mail, ok := changes.GetMailOk(); ok { if !isValidEmail(*mail) { errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("'%s' is not a valid email address", *mail)) return } + changedFeature = "email" + newValue = *mail } u, err := g.identityBackend.UpdateUser(r.Context(), nameOrID, *changes) @@ -229,6 +242,13 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { } } + g.publishEvent( + events.UserFeatureChanged{ + UserID: nameOrID, + Feature: changedFeature, + Value: newValue, + }, + ) render.Status(r, http.StatusOK) render.JSON(w, r, u)