All checks were successful
Build & push Docker image / build (push) Successful in 1m51s
162 lines
5.2 KiB
Go
162 lines
5.2 KiB
Go
package server_test
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"misaki/db"
|
|
"misaki/server"
|
|
)
|
|
|
|
func sessionGroups(t *testing.T, srv http.Handler, d *db.DB, user, pass string) (int, []string) {
|
|
t.Helper()
|
|
cookie := doLogin(t, srv, user, pass)
|
|
rr := doGet(srv, "/internal/session-check", cookie)
|
|
raw := rr.Header().Get("X-Auth-Groups")
|
|
if raw == "" {
|
|
return rr.Code, nil
|
|
}
|
|
g := strings.Split(raw, ",")
|
|
sort.Strings(g)
|
|
return rr.Code, g
|
|
}
|
|
|
|
func eqGroups(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
sort.Strings(b)
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestSessionCheck_FilesGroups(t *testing.T) {
|
|
srv, d := newTestServer(t)
|
|
|
|
makeApprovedUser(t, d, "plain", "hunter2hunter2")
|
|
if code, g := sessionGroups(t, srv, d, "plain", "hunter2hunter2"); code != http.StatusOK || !eqGroups(g, []string{"users"}) {
|
|
t.Errorf("plain user: code=%d groups=%v, want 200 [users]", code, g)
|
|
}
|
|
|
|
id := makeApprovedUser(t, d, "dj", "hunter2hunter2")
|
|
if err := d.SetUserServiceEnabled(id, "files", true); err != nil {
|
|
t.Fatalf("grant files: %v", err)
|
|
}
|
|
if err := d.SetUserServiceData(id, "files", "share,fmradio"); err != nil {
|
|
t.Fatalf("set mounts: %v", err)
|
|
}
|
|
if code, g := sessionGroups(t, srv, d, "dj", "hunter2hunter2"); code != http.StatusOK || !eqGroups(g, []string{"users", "share", "fmradio"}) {
|
|
t.Errorf("files user: code=%d groups=%v, want [users share fmradio]", code, g)
|
|
}
|
|
|
|
id2 := makeApprovedUser(t, d, "star", "hunter2hunter2")
|
|
if err := d.SetUserServiceEnabled(id2, "files", true); err != nil {
|
|
t.Fatalf("grant files: %v", err)
|
|
}
|
|
if err := d.SetUserServiceData(id2, "files", "*"); err != nil {
|
|
t.Fatalf("set mounts: %v", err)
|
|
}
|
|
if code, g := sessionGroups(t, srv, d, "star", "hunter2hunter2"); code != http.StatusOK || !eqGroups(g, []string{"users", "admin"}) {
|
|
t.Errorf("wildcard files user: code=%d groups=%v, want [users admin]", code, g)
|
|
}
|
|
|
|
makeAdminUser(t, d, "boss", "hunter2hunter2")
|
|
if code, g := sessionGroups(t, srv, d, "boss", "hunter2hunter2"); code != http.StatusOK || !eqGroups(g, []string{"users", "admin"}) {
|
|
t.Errorf("admin user: code=%d groups=%v, want [users admin]", code, g)
|
|
}
|
|
}
|
|
|
|
func TestSessionCheck_SignedIdentity(t *testing.T) {
|
|
const secret = "test-auth-secret"
|
|
d := newTestDB(t)
|
|
srv := server.New(d, server.ServiceURLs{}, nil, server.SMTPConfig{},
|
|
server.Config{AuthSecret: secret})
|
|
|
|
makeApprovedUser(t, d, "alice", "hunter2hunter2")
|
|
cookie := doLogin(t, srv, "alice", "hunter2hunter2")
|
|
rr := doGet(srv, "/internal/session-check", cookie)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("session-check: got %d, want 200", rr.Code)
|
|
}
|
|
|
|
sig := rr.Header().Get("X-Auth-Sig")
|
|
expRaw := rr.Header().Get("X-Auth-Exp")
|
|
if sig == "" || expRaw == "" {
|
|
t.Fatalf("missing signature headers: sig=%q exp=%q", sig, expRaw)
|
|
}
|
|
exp, err := strconv.ParseInt(expRaw, 10, 64)
|
|
if err != nil {
|
|
t.Fatalf("X-Auth-Exp not an int: %v", err)
|
|
}
|
|
if exp <= time.Now().Unix() {
|
|
t.Errorf("X-Auth-Exp %d is not in the future", exp)
|
|
}
|
|
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
fmt.Fprintf(mac, "v1:%s:%d", "alice", exp)
|
|
want := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
|
|
if sig != want {
|
|
t.Errorf("signature mismatch: got %q, want %q", sig, want)
|
|
}
|
|
|
|
mac2 := hmac.New(sha256.New, []byte(secret))
|
|
fmt.Fprintf(mac2, "v1:%s:%d", "admin", exp)
|
|
if forged := base64.RawURLEncoding.EncodeToString(mac2.Sum(nil)); forged == sig {
|
|
t.Error("signature collides for a different username")
|
|
}
|
|
}
|
|
|
|
func TestSessionCheck_NoSecretNoSignature(t *testing.T) {
|
|
srv, d := newTestServer(t)
|
|
makeApprovedUser(t, d, "bob", "hunter2hunter2")
|
|
cookie := doLogin(t, srv, "bob", "hunter2hunter2")
|
|
rr := doGet(srv, "/internal/session-check", cookie)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("session-check: got %d, want 200", rr.Code)
|
|
}
|
|
if sig := rr.Header().Get("X-Auth-Sig"); sig != "" {
|
|
t.Errorf("unexpected signature when no secret configured: %q", sig)
|
|
}
|
|
}
|
|
|
|
func TestServicesAreIndependent(t *testing.T) {
|
|
const secret = "test-secret"
|
|
srv2, d := newSecretServer(t, secret)
|
|
|
|
id := makeApprovedUser(t, d, "onlyradio", "hunter2hunter2")
|
|
if err := d.SetUserServiceEnabled(id, "radio", true); err != nil {
|
|
t.Fatalf("grant radio: %v", err)
|
|
}
|
|
if err := d.SetUserServiceData(id, "radio", "fmradio"); err != nil {
|
|
t.Fatalf("set stations: %v", err)
|
|
}
|
|
if rr := streamAuth(srv2, secret, "onlyradio", "hunter2hunter2", "fmradio"); rr.Code != http.StatusOK {
|
|
t.Errorf("radio push: got %d, want 200", rr.Code)
|
|
}
|
|
if code, g := sessionGroups(t, srv2, d, "onlyradio", "hunter2hunter2"); code != http.StatusOK || !eqGroups(g, []string{"users"}) {
|
|
t.Errorf("radio user copyparty groups: code=%d groups=%v, want [users]", code, g)
|
|
}
|
|
|
|
id2 := makeApprovedUser(t, d, "onlyfiles", "hunter2hunter2")
|
|
if err := d.SetUserServiceEnabled(id2, "files", true); err != nil {
|
|
t.Fatalf("grant files: %v", err)
|
|
}
|
|
if err := d.SetUserServiceData(id2, "files", "fmradio"); err != nil {
|
|
t.Fatalf("set mounts: %v", err)
|
|
}
|
|
if rr := streamAuth(srv2, secret, "onlyfiles", "hunter2hunter2", "fmradio"); rr.Code != http.StatusForbidden {
|
|
t.Errorf("files user harbor push: got %d, want 403", rr.Code)
|
|
}
|
|
}
|