feature: Log username with each request #422 (#424)

This commit is contained in:
James Read 2024-10-10 21:50:47 +01:00 committed by GitHub
parent 9737886839
commit 5d82ae7680
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 111 additions and 25 deletions

View File

@ -2,6 +2,7 @@ package acl
import ( import (
"context" "context"
config "github.com/OliveTin/OliveTin/internal/config" config "github.com/OliveTin/OliveTin/internal/config"
log "github.com/sirupsen/logrus" 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 // UserFromContext tries to find a user from a grpc context
func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser { func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser {
ret := &AuthenticatedUser{} var ret *AuthenticatedUser
md, ok := metadata.FromIncomingContext(ctx) md, ok := metadata.FromIncomingContext(ctx)
if ok { if ok {
ret = &AuthenticatedUser{}
ret.Username = getMetadataKeyOrEmpty(md, "username") ret.Username = getMetadataKeyOrEmpty(md, "username")
ret.Usergroup = getMetadataKeyOrEmpty(md, "usergroup") ret.Usergroup = getMetadataKeyOrEmpty(md, "usergroup")
buildUserAcls(cfg, ret)
} }
if ret.Username == "" { if !ok || ret.Username == "" {
ret.Username = "guest" ret = UserGuest(cfg)
} }
if ret.Usergroup == "" {
ret.Usergroup = "guest"
}
buildUserAcls(cfg, ret)
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"username": ret.Username, "username": ret.Username,
"usergroup": ret.Usergroup, "usergroup": ret.Usergroup,
@ -162,6 +160,16 @@ func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser
return ret 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 { func UserFromSystem(cfg *config.Config, username string) *AuthenticatedUser {
ret := &AuthenticatedUser{ ret := &AuthenticatedUser{
Username: username, Username: username,

View File

@ -84,6 +84,7 @@ type InternalLogEntry struct {
ExecutionFinished bool ExecutionFinished bool
ExecutionTrackingID string ExecutionTrackingID string
Process *os.Process Process *os.Process
Username string
/* /*
The following 3 properties are obviously on Action normally, but it's useful 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 // ExecRequest processes an ExecutionRequest
func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string) { func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string) {
if req.AuthenticatedUser == nil {
req.AuthenticatedUser = acl.UserGuest(req.Cfg)
}
req.executor = e req.executor = e
req.logEntry = &InternalLogEntry{ req.logEntry = &InternalLogEntry{
DatetimeStarted: time.Now(), DatetimeStarted: time.Now(),
@ -193,6 +198,7 @@ func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string)
ActionId: "", ActionId: "",
ActionTitle: "notfound", ActionTitle: "notfound",
ActionIcon: "💩", ActionIcon: "💩",
Username: req.AuthenticatedUser.Username,
} }
_, isDuplicate := e.GetLog(req.TrackingID) _, isDuplicate := e.GetLog(req.TrackingID)

View File

@ -2,6 +2,7 @@ package grpcapi
import ( import (
ctx "context" ctx "context"
pb "github.com/OliveTin/OliveTin/gen/grpc" pb "github.com/OliveTin/OliveTin/gen/grpc"
"github.com/google/uuid" "github.com/google/uuid"
log "github.com/sirupsen/logrus" 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.") return nil, status.Errorf(codes.NotFound, "Action not found.")
} }
authenticatedUser := acl.UserFromContext(ctx, cfg)
execReq := executor.ExecutionRequest{ execReq := executor.ExecutionRequest{
Action: pair.Action, Action: pair.Action,
EntityPrefix: pair.EntityPrefix, EntityPrefix: pair.EntityPrefix,
TrackingID: req.UniqueTrackingId, TrackingID: req.UniqueTrackingId,
Arguments: args, Arguments: args,
AuthenticatedUser: acl.UserFromContext(ctx, cfg), AuthenticatedUser: authenticatedUser,
Cfg: cfg, Cfg: cfg,
} }
@ -174,6 +177,7 @@ func internalLogEntryToPb(logEntry *executor.InternalLogEntry) *pb.LogEntry {
ExecutionTrackingId: logEntry.ExecutionTrackingID, ExecutionTrackingId: logEntry.ExecutionTrackingID,
ExecutionStarted: logEntry.ExecutionStarted, ExecutionStarted: logEntry.ExecutionStarted,
ExecutionFinished: logEntry.ExecutionFinished, ExecutionFinished: logEntry.ExecutionFinished,
User: logEntry.Username,
} }
} }

View File

@ -109,7 +109,7 @@ func exec(instant time.Time, action *config.Action, cfg *config.Config, ex *exec
req := &executor.ExecutionRequest{ req := &executor.ExecutionRequest{
Action: action, Action: action,
Cfg: cfg, Cfg: cfg,
Tags: []string{"calendar"}, Tags: []string{},
AuthenticatedUser: acl.UserFromSystem(cfg, "calendar"), AuthenticatedUser: acl.UserFromSystem(cfg, "calendar"),
} }

View File

@ -36,7 +36,7 @@ func scheduleAction(cfg *config.Config, scheduler *cron.Cron, cronline string, e
req := &executor.ExecutionRequest{ req := &executor.ExecutionRequest{
ActionTitle: action.Title, ActionTitle: action.Title,
Cfg: cfg, Cfg: cfg,
Tags: []string{"cron"}, Tags: []string{},
AuthenticatedUser: acl.UserFromSystem(cfg, "cron"), AuthenticatedUser: acl.UserFromSystem(cfg, "cron"),
} }

View File

@ -46,7 +46,7 @@ func scheduleExec(action *config.Action, cfg *config.Config, ex *executor.Execut
req := &executor.ExecutionRequest{ req := &executor.ExecutionRequest{
ActionTitle: action.Title, ActionTitle: action.Title,
Cfg: cfg, Cfg: cfg,
Tags: []string{"fileindir"}, Tags: []string{},
Arguments: args, Arguments: args,
AuthenticatedUser: acl.UserFromSystem(cfg, "fileindir"), AuthenticatedUser: acl.UserFromSystem(cfg, "fileindir"),
} }

View File

@ -8,7 +8,7 @@ import (
) )
func Execute(cfg *config.Config, ex *executor.Executor) { func Execute(cfg *config.Config, ex *executor.Executor) {
user := acl.UserFromSystem(cfg, "startup-user") user := acl.UserFromSystem(cfg, "startup")
for _, action := range cfg.Actions { for _, action := range cfg.Actions {
if action.ExecOnStartup { if action.ExecOnStartup {
@ -20,7 +20,7 @@ func Execute(cfg *config.Config, ex *executor.Executor) {
ActionTitle: action.Title, ActionTitle: action.Title,
Arguments: nil, Arguments: nil,
Cfg: cfg, Cfg: cfg,
Tags: []string{"startup"}, Tags: []string{},
AuthenticatedUser: user, AuthenticatedUser: user,
} }

View File

@ -1,14 +1,15 @@
package websocket package websocket
import ( import (
"net/http"
"sync"
pb "github.com/OliveTin/OliveTin/gen/grpc" pb "github.com/OliveTin/OliveTin/gen/grpc"
"github.com/OliveTin/OliveTin/internal/executor" "github.com/OliveTin/OliveTin/internal/executor"
ws "github.com/gorilla/websocket" ws "github.com/gorilla/websocket"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoreflect"
"net/http"
"sync"
) )
var upgrader = ws.Upgrader{ var upgrader = ws.Upgrader{
@ -95,9 +96,12 @@ func (WebsocketExecutionListener) OnExecutionFinished(logEntry *executor.Interna
ExecutionTrackingId: logEntry.ExecutionTrackingID, ExecutionTrackingId: logEntry.ExecutionTrackingID,
ExecutionStarted: logEntry.ExecutionStarted, ExecutionStarted: logEntry.ExecutionStarted,
ExecutionFinished: logEntry.ExecutionFinished, ExecutionFinished: logEntry.ExecutionFinished,
User: logEntry.Username,
}, },
} }
log.Infof("Execution finished: %v+v", evt.LogEntry)
broadcast(evt) broadcast(evt)
} }

View File

@ -58,7 +58,7 @@
<tr title = "untitled"> <tr title = "untitled">
<th>Timestamp</th> <th>Timestamp</th>
<th>Action</th> <th>Action</th>
<th>Tags</th> <th>Metadata</th>
<th>Status</th> <th>Status</th>
</tr> </tr>
</thead> </thead>

View File

@ -2,6 +2,49 @@ import './ActionButton.js' // To define action-button
import { ExecutionDialog } from './ExecutionDialog.js' import { ExecutionDialog } from './ExecutionDialog.js'
import { ActionStatusDisplay } from './ActionStatusDisplay.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. * This is a weird function that just sets some globals.
*/ */
@ -577,13 +620,12 @@ export function marshalLogsJsonToHtml (json) {
} }
for (const tag of logEntry.tags) { for (const tag of logEntry.tags) {
const domTag = document.createElement('span') row.querySelector('.tags').append(createTag(tag))
domTag.classList.add('tag')
domTag.innerText = tag
row.querySelector('.tags').append(domTag)
} }
console.log(logEntry)
row.querySelector('.tags').append(createAnnotation('user', logEntry.user))
document.querySelector('#logTableBody').prepend(row) document.querySelector('#logTableBody').prepend(row)
} }
} }

View File

@ -457,12 +457,34 @@ input.invalid {
} }
span.tag { span.tag {
background-color: lightgray;
border-radius: 0.4em; border-radius: 0.4em;
padding: 0.4em;
margin-top: .2em; margin-top: .2em;
margin-right: .2em; margin-right: .2em;
display: inline-block; 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; color: black;
} }