add post event handler

Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
jkoberg
2023-06-29 15:33:17 +02:00
parent 6a9e924ad3
commit bca4d4f9fd
4 changed files with 169 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"embed"
"encoding/json"
"fmt"
"io/fs"
"strings"
@@ -28,6 +29,7 @@ var (
_resourceTypeResource = "resource"
_resourceTypeSpace = "storagespace"
_resourceTypeShare = "share"
_resourceTypeGlobal = "global"
_domain = "userlog"
)
@@ -116,6 +118,22 @@ func (c *Converter) ConvertEvent(eventid string, event interface{}) (OC10Notific
}
}
// ConvertGlobalEvent converts a global event to an OC10Notification
func (c *Converter) ConvertGlobalEvent(typ string, data json.RawMessage) (OC10Notification, error) {
switch typ {
default:
return OC10Notification{}, fmt.Errorf("unknown global event type: %s", typ)
case "deprovision":
var dd DeprovisionData
if err := json.Unmarshal(data, &dd); err != nil {
return OC10Notification{}, err
}
return c.deprovisionMessage(PlatformDeprovision, dd.DeprovisionDate)
}
}
func (c *Converter) spaceDeletedMessage(eventid string, executant *user.UserId, spaceid string, spacename string, ts time.Time) (OC10Notification, error) {
usr, err := c.getUser(context.Background(), executant)
if err != nil {
@@ -287,6 +305,28 @@ func (c *Converter) policiesMessage(eventid string, nt NotificationTemplate, exe
}, nil
}
func (c *Converter) deprovisionMessage(nt NotificationTemplate, deproDate string) (OC10Notification, error) {
subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.translationPath, map[string]interface{}{
"date": deproDate,
})
if err != nil {
return OC10Notification{}, err
}
return OC10Notification{
EventID: "deprovision",
Service: c.serviceName,
// UserName: executant.GetUsername(), // TODO: do we need the deprovisioner?
Timestamp: time.Now().Format(time.RFC3339Nano), // Fake timestamp? Or we store one with the event?
ResourceType: _resourceTypeResource,
Subject: subj,
SubjectRaw: subjraw,
Message: msg,
MessageRaw: msgraw,
MessageDetails: map[string]interface{}{},
}, nil
}
func (c *Converter) authenticate(usr *user.User) (context.Context, error) {
if ctx, ok := c.contexts[usr.GetId().GetOpaqueId()]; ok {
return ctx, nil

View File

@@ -57,6 +57,23 @@ func (ul *UserlogService) HandleGetEvents(w http.ResponseWriter, r *http.Request
resp.OCS.Data = append(resp.OCS.Data, noti)
}
glevs, err := ul.GetGlobalEvents()
if err != nil {
ul.log.Error().Err(err).Int("returned statuscode", http.StatusInternalServerError).Msg("get global events failed")
w.WriteHeader(http.StatusInternalServerError)
return
}
for t, data := range glevs {
noti, err := conv.ConvertGlobalEvent(t, data)
if err != nil {
ul.log.Error().Err(err).Str("eventtype", t).Msg("failed to convert event")
continue
}
resp.OCS.Data = append(resp.OCS.Data, noti)
}
resp.OCS.Meta.StatusCode = http.StatusOK
b, _ := json.Marshal(resp)
w.Write(b)
@@ -89,6 +106,40 @@ func (ul *UserlogService) HandleSSE(w http.ResponseWriter, r *http.Request) {
ul.sse.ServeHTTP(w, r)
}
// HandlePostEvent is the POST handler for events
func (ul *UserlogService) HandlePostEvent(w http.ResponseWriter, r *http.Request) {
u, ok := ctx.ContextGetUser(r.Context())
if !ok {
ul.log.Error().Msg("post: no user in context")
w.WriteHeader(http.StatusInternalServerError)
return
}
uid := u.GetId().GetOpaqueId()
if uid == "" {
ul.log.Error().Msg("post: user in context is broken")
w.WriteHeader(http.StatusInternalServerError)
return
}
// TODO: Check user is allowed to do this
var req PostEventsRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
ul.log.Error().Err(err).Int("returned statuscode", http.StatusBadRequest).Msg("request body is malformed")
w.WriteHeader(http.StatusBadRequest)
return
}
if err := ul.StoreGlobalEvent(req.Type, req.Data); err != nil {
ul.log.Error().Err(err).Msg("post: error storing global event")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
// HandleDeleteEvents is the DELETE handler for events
func (ul *UserlogService) HandleDeleteEvents(w http.ResponseWriter, r *http.Request) {
u, ok := revactx.ContextGetUser(r.Context())
@@ -130,3 +181,14 @@ type GetEventResponseOC10 struct {
type DeleteEventsRequest struct {
IDs []string `json:"ids"`
}
// PostEventsRequest is the expected body for the post request
type PostEventsRequest struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
// DeprovisionData is the expected `data` for the PostEventsRequest when deprovisioning
type DeprovisionData struct {
DeprovisionDate string `json:"date"`
}

View File

@@ -82,6 +82,7 @@ func NewUserlogService(opts ...Option) (*UserlogService, error) {
ul.m.Route("/ocs/v2.php/apps/notifications/api/v1/notifications", func(r chi.Router) {
r.Get("/", ul.HandleGetEvents)
r.Post("/", ul.HandlePostEvent)
r.Delete("/", ul.HandleDeleteEvents)
if !ul.cfg.DisableSSE {
@@ -235,6 +236,42 @@ func (ul *UserlogService) DeleteEvents(userid string, evids []string) error {
})
}
// StoreGlobalEvent will store a global event that will be returned with each `GetEvents` request
func (ul *UserlogService) StoreGlobalEvent(typ string, data json.RawMessage) error {
switch typ {
default:
return fmt.Errorf("unknown event type: %s", typ)
case "deprovision":
var req DeprovisionData
if err := json.Unmarshal(data, &req); err != nil {
return err
}
// TODO: check for proper time format
return ul.storeGlobalEvent(typ, req)
}
}
// GetGlobalEvents will return all global events
func (ul *UserlogService) GetGlobalEvents() (map[string]json.RawMessage, error) {
out := make(map[string]json.RawMessage)
recs, err := ul.store.Read("global-events")
if err != nil && err != store.ErrNotFound {
return out, err
}
if len(recs) > 0 {
if err := json.Unmarshal(recs[0].Value, &out); err != nil {
return out, err
}
}
return out, nil
}
func (ul *UserlogService) addEventToUser(userid string, event events.Event) error {
if !ul.cfg.DisableSSE {
if err := ul.sendSSE(userid, event); err != nil {
@@ -312,6 +349,30 @@ func (ul *UserlogService) alterUserEventList(userid string, alter func([]string)
})
}
func (ul *UserlogService) storeGlobalEvent(typ string, ev interface{}) error {
b, err := json.Marshal(ev)
if err != nil {
return err
}
evs, err := ul.GetGlobalEvents()
if err != nil && err != store.ErrNotFound {
return err
}
evs[typ] = b
val, err := json.Marshal(evs)
if err != nil {
return err
}
return ul.store.Write(&store.Record{
Key: "global-events",
Value: val,
})
}
// we need the spaceid to inform other space members
// we need an owner to query space members
// we need to check the user has the required role to see the event

View File

@@ -54,6 +54,11 @@ var (
Subject: Template("Share expired"),
Message: Template("Access to {resource} expired"),
}
PlatformDeprovision = NotificationTemplate{
Subject: Template("Platform will be deprovisioned"),
Message: Template("Attention! The platform will be deprovisioned at {date}"),
}
)
// holds the information to turn the raw template into a parseable go template
@@ -62,6 +67,7 @@ var _placeholders = map[string]string{
"{space}": "{{ .spacename }}",
"{resource}": "{{ .resourcename }}",
"{virus}": "{{ .virusdescription }}",
"{date}": "{{ .date }}",
}
// NotificationTemplate is the data structure for the notifications