Major changes: logic add to panel, local variables, panel histor, users management...
This commit is contained in:
+30
-1
@@ -6,11 +6,13 @@ import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
|
||||
"github.com/uopi/uopi/internal/access"
|
||||
"github.com/uopi/uopi/internal/broker"
|
||||
"github.com/uopi/uopi/internal/datasource"
|
||||
"github.com/uopi/uopi/internal/metrics"
|
||||
@@ -87,9 +89,21 @@ type histPoint struct {
|
||||
type wsHandler struct {
|
||||
broker *broker.Broker
|
||||
log *slog.Logger
|
||||
// userHeader is the HTTP header from which the per-session end-user identity
|
||||
// is read (set by a trusted auth proxy). Empty disables per-user identity.
|
||||
userHeader string
|
||||
// policy enforces the global access level (blacklist) on signal writes.
|
||||
policy *access.Policy
|
||||
}
|
||||
|
||||
func (h *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Capture the end-user identity from the trusted proxy header (if enabled)
|
||||
// before the connection is upgraded, while the original headers are present.
|
||||
var user string
|
||||
if h.userHeader != "" {
|
||||
user = strings.TrimSpace(r.Header.Get(h.userHeader))
|
||||
}
|
||||
|
||||
conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||
// Permissive origin check for LAN / SSH-tunnel use; tighten when auth lands.
|
||||
InsecureSkipVerify: true,
|
||||
@@ -107,6 +121,8 @@ func (h *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
c := &wsClient{
|
||||
conn: conn,
|
||||
broker: h.broker,
|
||||
user: user,
|
||||
policy: h.policy,
|
||||
outCh: make(chan []byte, 512),
|
||||
updateCh: make(chan broker.Update, 1024),
|
||||
subs: make(map[broker.SignalRef]func()),
|
||||
@@ -136,6 +152,8 @@ type wsClient struct {
|
||||
conn *websocket.Conn
|
||||
broker *broker.Broker
|
||||
log *slog.Logger
|
||||
user string // end-user identity from the trusted proxy header ("" if none)
|
||||
policy *access.Policy // global access-level enforcement
|
||||
|
||||
outCh chan []byte // serialised outgoing messages
|
||||
updateCh chan broker.Update // raw updates from the broker
|
||||
@@ -277,6 +295,14 @@ func (c *wsClient) handleUnsubscribe(refs []sigRef) {
|
||||
}
|
||||
|
||||
func (c *wsClient) handleWrite(ctx context.Context, msg inMsg) {
|
||||
// Enforce the user's global access level: blacklisted read-only / no-access
|
||||
// users may not write signals, regardless of the channel's own writability.
|
||||
if c.policy != nil && c.policy.Level(c.policy.ResolveUser(c.user)) < access.LevelWrite {
|
||||
c.log.Warn("write: access denied", "ds", msg.DS, "name", msg.Name, "user", c.user)
|
||||
c.sendError(ctx, "ACCESS_DENIED", "you do not have write access")
|
||||
return
|
||||
}
|
||||
|
||||
ds, ok := c.broker.Source(msg.DS)
|
||||
if !ok {
|
||||
c.log.Warn("write: unknown data source", "ds", msg.DS, "name", msg.Name)
|
||||
@@ -305,8 +331,11 @@ func (c *wsClient) handleWrite(ctx context.Context, msg inMsg) {
|
||||
return
|
||||
}
|
||||
|
||||
c.log.Info("write", "ds", msg.DS, "name", msg.Name, "value", value)
|
||||
c.log.Info("write", "ds", msg.DS, "name", msg.Name, "value", value, "user", c.user)
|
||||
metrics.IncWrites()
|
||||
// Attribute the write to the connecting end-user so data sources (EPICS) can
|
||||
// act under that identity rather than the server's.
|
||||
ctx = datasource.WithUser(ctx, c.user)
|
||||
if err := ds.Write(ctx, msg.Name, value); err != nil {
|
||||
c.log.Warn("write: ds.Write failed", "ds", msg.DS, "name", msg.Name, "err", err)
|
||||
c.sendError(ctx, "WRITE_ERROR", err.Error())
|
||||
|
||||
Reference in New Issue
Block a user