From 5d82ae7680bdbefe0604072d206e989373f22bc8 Mon Sep 17 00:00:00 2001 From: James Read Date: Thu, 10 Oct 2024 21:50:47 +0100 Subject: [PATCH] feature: Log username with each request #422 (#424) --- internal/acl/acl.go | 26 ++++++++++----- internal/executor/executor.go | 6 ++++ internal/grpcapi/grpcApi.go | 6 +++- internal/oncalendarfile/calendar.go | 2 +- internal/oncron/cron.go | 2 +- internal/onfileindir/fileindir.go | 2 +- internal/onstartup/startup.go | 4 +-- internal/websocket/websocket.go | 8 +++-- webui.dev/index.html | 2 +- webui.dev/js/marshaller.js | 52 ++++++++++++++++++++++++++--- webui.dev/style.css | 26 +++++++++++++-- 11 files changed, 111 insertions(+), 25 deletions(-) diff --git a/internal/acl/acl.go b/internal/acl/acl.go index 6fb6c6c..3562071 100644 --- a/internal/acl/acl.go +++ b/internal/acl/acl.go @@ -2,6 +2,7 @@ package acl import ( "context" + config "github.com/OliveTin/OliveTin/internal/config" log "github.com/sirupsen/logrus" @@ -135,25 +136,22 @@ func getMetadataKeyOrEmpty(md metadata.MD, key string) string { // UserFromContext tries to find a user from a grpc context func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser { - ret := &AuthenticatedUser{} + var ret *AuthenticatedUser md, ok := metadata.FromIncomingContext(ctx) if ok { + ret = &AuthenticatedUser{} ret.Username = getMetadataKeyOrEmpty(md, "username") ret.Usergroup = getMetadataKeyOrEmpty(md, "usergroup") + + buildUserAcls(cfg, ret) } - if ret.Username == "" { - ret.Username = "guest" + if !ok || ret.Username == "" { + ret = UserGuest(cfg) } - if ret.Usergroup == "" { - ret.Usergroup = "guest" - } - - buildUserAcls(cfg, ret) - log.WithFields(log.Fields{ "username": ret.Username, "usergroup": ret.Usergroup, @@ -162,6 +160,16 @@ func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser return ret } +func UserGuest(cfg *config.Config) *AuthenticatedUser { + ret := &AuthenticatedUser{} + ret.Username = "guest" + ret.Usergroup = "guest" + + buildUserAcls(cfg, ret) + + return ret +} + func UserFromSystem(cfg *config.Config, username string) *AuthenticatedUser { ret := &AuthenticatedUser{ Username: username, diff --git a/internal/executor/executor.go b/internal/executor/executor.go index 3612ef3..23b2ae7 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -84,6 +84,7 @@ type InternalLogEntry struct { ExecutionFinished bool ExecutionTrackingID string Process *os.Process + Username string /* The following 3 properties are obviously on Action normally, but it's useful @@ -182,6 +183,10 @@ func (e *Executor) SetLog(trackingID string, entry *InternalLogEntry) { // ExecRequest processes an ExecutionRequest func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string) { + if req.AuthenticatedUser == nil { + req.AuthenticatedUser = acl.UserGuest(req.Cfg) + } + req.executor = e req.logEntry = &InternalLogEntry{ DatetimeStarted: time.Now(), @@ -193,6 +198,7 @@ func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string) ActionId: "", ActionTitle: "notfound", ActionIcon: "💩", + Username: req.AuthenticatedUser.Username, } _, isDuplicate := e.GetLog(req.TrackingID) diff --git a/internal/grpcapi/grpcApi.go b/internal/grpcapi/grpcApi.go index 5597080..b239409 100644 --- a/internal/grpcapi/grpcApi.go +++ b/internal/grpcapi/grpcApi.go @@ -2,6 +2,7 @@ package grpcapi import ( ctx "context" + pb "github.com/OliveTin/OliveTin/gen/grpc" "github.com/google/uuid" log "github.com/sirupsen/logrus" @@ -75,12 +76,14 @@ func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest) return nil, status.Errorf(codes.NotFound, "Action not found.") } + authenticatedUser := acl.UserFromContext(ctx, cfg) + execReq := executor.ExecutionRequest{ Action: pair.Action, EntityPrefix: pair.EntityPrefix, TrackingID: req.UniqueTrackingId, Arguments: args, - AuthenticatedUser: acl.UserFromContext(ctx, cfg), + AuthenticatedUser: authenticatedUser, Cfg: cfg, } @@ -174,6 +177,7 @@ func internalLogEntryToPb(logEntry *executor.InternalLogEntry) *pb.LogEntry { ExecutionTrackingId: logEntry.ExecutionTrackingID, ExecutionStarted: logEntry.ExecutionStarted, ExecutionFinished: logEntry.ExecutionFinished, + User: logEntry.Username, } } diff --git a/internal/oncalendarfile/calendar.go b/internal/oncalendarfile/calendar.go index 42f8cde..97658b1 100644 --- a/internal/oncalendarfile/calendar.go +++ b/internal/oncalendarfile/calendar.go @@ -109,7 +109,7 @@ func exec(instant time.Time, action *config.Action, cfg *config.Config, ex *exec req := &executor.ExecutionRequest{ Action: action, Cfg: cfg, - Tags: []string{"calendar"}, + Tags: []string{}, AuthenticatedUser: acl.UserFromSystem(cfg, "calendar"), } diff --git a/internal/oncron/cron.go b/internal/oncron/cron.go index 358166b..97dd687 100644 --- a/internal/oncron/cron.go +++ b/internal/oncron/cron.go @@ -36,7 +36,7 @@ func scheduleAction(cfg *config.Config, scheduler *cron.Cron, cronline string, e req := &executor.ExecutionRequest{ ActionTitle: action.Title, Cfg: cfg, - Tags: []string{"cron"}, + Tags: []string{}, AuthenticatedUser: acl.UserFromSystem(cfg, "cron"), } diff --git a/internal/onfileindir/fileindir.go b/internal/onfileindir/fileindir.go index 6163746..859c282 100644 --- a/internal/onfileindir/fileindir.go +++ b/internal/onfileindir/fileindir.go @@ -46,7 +46,7 @@ func scheduleExec(action *config.Action, cfg *config.Config, ex *executor.Execut req := &executor.ExecutionRequest{ ActionTitle: action.Title, Cfg: cfg, - Tags: []string{"fileindir"}, + Tags: []string{}, Arguments: args, AuthenticatedUser: acl.UserFromSystem(cfg, "fileindir"), } diff --git a/internal/onstartup/startup.go b/internal/onstartup/startup.go index 8cd755e..303489a 100644 --- a/internal/onstartup/startup.go +++ b/internal/onstartup/startup.go @@ -8,7 +8,7 @@ import ( ) func Execute(cfg *config.Config, ex *executor.Executor) { - user := acl.UserFromSystem(cfg, "startup-user") + user := acl.UserFromSystem(cfg, "startup") for _, action := range cfg.Actions { if action.ExecOnStartup { @@ -20,7 +20,7 @@ func Execute(cfg *config.Config, ex *executor.Executor) { ActionTitle: action.Title, Arguments: nil, Cfg: cfg, - Tags: []string{"startup"}, + Tags: []string{}, AuthenticatedUser: user, } diff --git a/internal/websocket/websocket.go b/internal/websocket/websocket.go index dd8ab3f..9dd5e27 100644 --- a/internal/websocket/websocket.go +++ b/internal/websocket/websocket.go @@ -1,14 +1,15 @@ package websocket import ( + "net/http" + "sync" + pb "github.com/OliveTin/OliveTin/gen/grpc" "github.com/OliveTin/OliveTin/internal/executor" ws "github.com/gorilla/websocket" log "github.com/sirupsen/logrus" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/reflect/protoreflect" - "net/http" - "sync" ) var upgrader = ws.Upgrader{ @@ -95,9 +96,12 @@ func (WebsocketExecutionListener) OnExecutionFinished(logEntry *executor.Interna ExecutionTrackingId: logEntry.ExecutionTrackingID, ExecutionStarted: logEntry.ExecutionStarted, ExecutionFinished: logEntry.ExecutionFinished, + User: logEntry.Username, }, } + log.Infof("Execution finished: %v+v", evt.LogEntry) + broadcast(evt) } diff --git a/webui.dev/index.html b/webui.dev/index.html index 1f89922..1b089ac 100644 --- a/webui.dev/index.html +++ b/webui.dev/index.html @@ -58,7 +58,7 @@ Timestamp Action - Tags + Metadata Status diff --git a/webui.dev/js/marshaller.js b/webui.dev/js/marshaller.js index b8deee3..ceef2c8 100644 --- a/webui.dev/js/marshaller.js +++ b/webui.dev/js/marshaller.js @@ -2,6 +2,49 @@ import './ActionButton.js' // To define action-button import { ExecutionDialog } from './ExecutionDialog.js' import { ActionStatusDisplay } from './ActionStatusDisplay.js' +function createElement (tag, attributes) { + const el = document.createElement(tag) + + if (attributes !== null) { + if (attributes.classNames !== undefined) { + el.classList.add(...attributes.classNames) + } + + if (attributes.innerText !== undefined) { + el.innerText = attributes.innerText + } + } + + return el +} + +function createTag (val) { + const domTag = createElement('span', { + innerText: val, + classNames: ['tag'] + }) + + return domTag +} + +function createAnnotation (key, val) { + const domAnnotation = createElement('span', { + classNames: ['annotation'] + }) + + domAnnotation.appendChild(createElement('span', { + innerText: key, + classNames: ['annotationKey'] + })) + + domAnnotation.appendChild(createElement('span', { + innerText: val, + classNames: ['annotationValue'] + })) + + return domAnnotation +} + /** * This is a weird function that just sets some globals. */ @@ -577,13 +620,12 @@ export function marshalLogsJsonToHtml (json) { } for (const tag of logEntry.tags) { - const domTag = document.createElement('span') - domTag.classList.add('tag') - domTag.innerText = tag - - row.querySelector('.tags').append(domTag) + row.querySelector('.tags').append(createTag(tag)) } + console.log(logEntry) + row.querySelector('.tags').append(createAnnotation('user', logEntry.user)) + document.querySelector('#logTableBody').prepend(row) } } diff --git a/webui.dev/style.css b/webui.dev/style.css index 02792b6..c02f3cf 100644 --- a/webui.dev/style.css +++ b/webui.dev/style.css @@ -457,12 +457,34 @@ input.invalid { } span.tag { - background-color: lightgray; border-radius: 0.4em; - padding: 0.4em; margin-top: .2em; margin-right: .2em; display: inline-block; + background-color: lightgray; + padding: .4em; + color: black; +} + +span.annotation { + display: inline-block; + margin-top: .2em; + margin-right: .2em; +} + +span.annotationKey { + padding: 0.4em; + border-radius: 0.4em 0 0 .4em; + display: inline-block; + background-color: lightgray; + color: #666; +} + +span.annotationValue { + padding: 0.4em; + border-radius: 0 .4em .4em 0; + display: inline-block; + background-color: lightgray; color: black; }