Provider-Wechsel nur für Admin (403 für Nutzer); App blendet Provider-Auswahl für Nicht-Admins aus; v0.3.1
This commit is contained in:
parent
161bc02bb9
commit
70d316851a
12 changed files with 102 additions and 17 deletions
43
internal/server/admin_test.go
Normal file
43
internal/server/admin_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProviderChangesAdminOnly(t *testing.T) {
|
||||
ts, _ := setupFull(t, echo{})
|
||||
ut := registerUser(t, ts.URL, "alice", "correct-horse-1")
|
||||
uh := bearerHdr(ut)
|
||||
if r := do(t, "GET", ts.URL+"/api/providers", "", uh); r.StatusCode != 200 {
|
||||
t.Fatal("users may list providers", r.StatusCode)
|
||||
}
|
||||
if r := do(t, "PUT", ts.URL+"/api/providers/active", `{"provider":"dahl"}`, uh); r.StatusCode != 403 {
|
||||
t.Fatal("user must not change global provider", r.StatusCode)
|
||||
}
|
||||
var me map[string]any
|
||||
json.NewDecoder(do(t, "GET", ts.URL+"/api/auth/me", "", uh).Body).Decode(&me)
|
||||
if me["admin"] != false {
|
||||
t.Fatalf("%v", me)
|
||||
}
|
||||
// own session: chat ok, provider change forbidden
|
||||
r := do(t, "POST", ts.URL+"/api/chat", `{"text":"hi"}`, uh)
|
||||
if r.StatusCode != 200 {
|
||||
t.Fatal("user chat", r.StatusCode)
|
||||
}
|
||||
var cr struct {
|
||||
SessionID string `json:"session_id"`
|
||||
}
|
||||
json.NewDecoder(r.Body).Decode(&cr)
|
||||
if cr.SessionID != "" {
|
||||
if r := do(t, "PUT", ts.URL+"/api/sessions/"+cr.SessionID+"/provider", `{"provider":"openai"}`, uh); r.StatusCode != 403 {
|
||||
t.Fatal("session provider", r.StatusCode)
|
||||
}
|
||||
}
|
||||
if r := do(t, "POST", ts.URL+"/api/chat", `{"text":"hi","provider":"dahl"}`, uh); r.StatusCode != 403 {
|
||||
t.Fatal("chat with provider", r.StatusCode)
|
||||
}
|
||||
if r := do(t, "PUT", ts.URL+"/api/providers/active", `{"provider":"dahl"}`, authH); r.StatusCode != 200 {
|
||||
t.Fatal("admin allowed", r.StatusCode)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,9 @@ func (s *Server) listProviders(w http.ResponseWriter, _ *http.Request) {
|
|||
}
|
||||
|
||||
func (s *Server) setActiveProvider(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireAdmin(w, r) {
|
||||
return
|
||||
}
|
||||
if !s.needProviders(w) {
|
||||
return
|
||||
}
|
||||
|
|
@ -82,6 +85,9 @@ func (s *Server) listModels(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// PUT /api/sessions/{id}/provider: per-session override; empty provider clears it.
|
||||
func (s *Server) setSessionProvider(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireAdmin(w, r) {
|
||||
return
|
||||
}
|
||||
if !s.needProviders(w) {
|
||||
return
|
||||
}
|
||||
|
|
@ -148,3 +154,15 @@ func (s *Server) getSummary(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
writeJSON(w, 200, sm)
|
||||
}
|
||||
|
||||
// errAdminOnly is returned when a non-admin tries to change provider settings.
|
||||
var errAdminOnly = errors.New("admin only: provider settings can only be changed by the server admin")
|
||||
|
||||
// requireAdmin writes 403 unless the caller is the admin (static auth_token).
|
||||
func requireAdmin(w http.ResponseWriter, r *http.Request) bool {
|
||||
if principal(r.Context()).Admin {
|
||||
return true
|
||||
}
|
||||
writeErr(w, http.StatusForbidden, errAdminOnly.Error())
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func setupFull(t *testing.T, p llm.Provider) (*httptest.Server, *store.Store) {
|
|||
pr, _, err := pm.ForSession(ctx, sid)
|
||||
return pr, err
|
||||
}
|
||||
s := &Server{Store: st, Token: tok, Log: slog.New(slog.NewTextHandler(io.Discard, nil)), RunTimeout: 5 * time.Second, Agent: ag, Providers: pm}
|
||||
s := &Server{Store: st, Token: tok, AllowRegistration: true, Log: slog.New(slog.NewTextHandler(io.Discard, nil)), RunTimeout: 5 * time.Second, Agent: ag, Providers: pm}
|
||||
ts := httptest.NewServer(s.Handler())
|
||||
t.Cleanup(ts.Close)
|
||||
return ts, st
|
||||
|
|
|
|||
|
|
@ -253,6 +253,9 @@ type chatReq struct {
|
|||
func (s *Server) startSession(ctx context.Context, req chatReq) (string, error) {
|
||||
var a providers.Active
|
||||
if req.SessionID == "" && req.Provider != "" && s.Providers != nil {
|
||||
if !principal(ctx).Admin {
|
||||
return "", errAdminOnly
|
||||
}
|
||||
var err error
|
||||
if a, err = s.Providers.Validate(req.Provider, req.Model); err != nil {
|
||||
return "", err
|
||||
|
|
@ -288,6 +291,10 @@ func (s *Server) chat(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
id, err := s.startSession(r.Context(), req)
|
||||
if errors.Is(err, errAdminOnly) {
|
||||
writeErr(w, http.StatusForbidden, err.Error())
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
if req.SessionID == "" && req.Provider != "" {
|
||||
providerErr(w, err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue