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
11 changed files with 111 additions and 25 deletions

View File

@@ -2,6 +2,7 @@ package acl
import (
"context"
config "github.com/OliveTin/OliveTin/internal/config"
log "github.com/sirupsen/logrus"
@@ -135,24 +136,21 @@ 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")
}
if ret.Username == "" {
ret.Username = "guest"
}
if ret.Usergroup == "" {
ret.Usergroup = "guest"
}
buildUserAcls(cfg, ret)
}
if !ok || ret.Username == "" {
ret = UserGuest(cfg)
}
log.WithFields(log.Fields{
"username": ret.Username,
@@ -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,

View File

@@ -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)

View File

@@ -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,
}
}

View File

@@ -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"),
}

View File

@@ -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"),
}

View File

@@ -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"),
}

View File

@@ -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,
}

View File

@@ -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)
}

View File

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

View File

@@ -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)
}
}

View File

@@ -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;
}