You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
themis/audit_log.go

116 lines
2.7 KiB

package themis
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"github.com/rs/zerolog/log"
)
type EventType int
const (
EventFlush EventType = iota
EventClaim
EventUnclaim
EventAbsence
)
func (et EventType) String() string {
switch et {
case EventFlush:
return "FLUSH"
case EventClaim:
return "CLAIM"
case EventUnclaim:
return "UNCLAIM"
case EventAbsence:
return "ABSENT"
default:
return ""
}
}
func EventTypeFromString(ev string) (EventType, error) {
switch ev {
case "FLUSH":
return EventFlush, nil
case "CLAIM":
return EventClaim, nil
case "UNCLAIM":
return EventUnclaim, nil
case "ABSENT":
return EventAbsence, nil
default:
return EventType(9999), errors.New("no such event type")
}
}
type AuditableEvent struct {
userId string
eventType EventType
timestamp time.Time
err error
}
// Audit writes to the audit table, returns nothing because it is meant to be
// used in a defered statement on functions that write to the database.
func (s *Store) Audit(ev *AuditableEvent) {
if ev.err == nil {
log.Debug().Str("event_type", ev.eventType.String()).Str("userid", ev.userId).Msg("recording audit log")
ctx := context.Background()
tx, err := s.db.Begin()
if err != nil {
log.Error().Err(err).Msg("failed to start transaction")
}
defer tx.Commit() //nolint:errcheck
stmt, err := s.db.PrepareContext(ctx, "INSERT INTO audit_log (userid, event_type, ts) VALUES (?, ?, ?)")
if err != nil {
log.Error().Err(err).Msg("failed to prepare audit log insert")
}
if _, err := stmt.ExecContext(ctx, ev.userId, ev.eventType.String(), time.Now()); err != nil {
log.Error().Err(err).Msg("failed to insert audit log")
}
}
}
type AuditEvent struct {
id int
userId string
eventType EventType
timestamp time.Time
}
func (s *Store) LastOf(ctx context.Context, t EventType) (AuditEvent, error) {
log.Debug().Str("event_type", t.String()).Msg("finding last audit log")
stmt, err := s.db.PrepareContext(ctx, `SELECT id, userid, event_type, ts FROM audit_log WHERE event_type = ? ORDER BY ts DESC LIMIT 1`)
if err != nil {
return AuditEvent{}, fmt.Errorf("failed to get last event of type %s: %w", t.String(), err)
}
row := stmt.QueryRowContext(ctx, t.String())
ev := AuditEvent{}
var rawEventType string
err = row.Scan(&ev.id, &ev.userId, &rawEventType, &ev.timestamp)
if err == sql.ErrNoRows {
return AuditEvent{}, errors.New("no rows found")
}
if err != nil {
return AuditEvent{}, fmt.Errorf("failed to scan row: %w", err)
}
ev.eventType, err = EventTypeFromString(rawEventType)
if err != nil {
return AuditEvent{}, fmt.Errorf("failed to parse event type %s: %w", rawEventType, err)
}
return ev, nil
}