Rename group package and defaults to halls

This commit is contained in:
Storm Dragon
2026-05-17 22:37:38 -04:00
parent 55528b8e62
commit a8ada950d5
26 changed files with 459 additions and 459 deletions
+1 -1
View File
@@ -2,6 +2,6 @@
/skald
/skaldctl/skaldctl
/data
/groups/**/*.json
/halls/**/*.json
/static/**/*.d.ts
/static/third-party/tasks-vision
+2 -2
View File
@@ -10,8 +10,8 @@ hall conferencing server. The current development repository is
git clone https://git.stormux.org/storm/skald
cd skald
CGO_ENABLED=0 go build -ldflags='-s -w'
mkdir groups
echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/night-watch.json
mkdir halls
echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > halls/night-watch.json
./skald &
```
+15 -15
View File
@@ -24,7 +24,7 @@ import (
gcodecs "git.stormux.org/storm/skald/codecs"
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/rtptime"
)
@@ -36,7 +36,7 @@ const (
var Directory string
type Client struct {
group *group.Group
hall *hall.Hall
id string
mu sync.Mutex
@@ -50,12 +50,12 @@ func newId() string {
return hex.EncodeToString(b)
}
func New(g *group.Group) *Client {
return &Client{group: g, id: newId()}
func New(g *hall.Hall) *Client {
return &Client{hall: g, id: newId()}
}
func (client *Client) Group() *group.Group {
return client.group
func (client *Client) Hall() *hall.Hall {
return client.hall
}
func (client *Client) Id() string {
@@ -86,7 +86,7 @@ func (client *Client) PushClient(group, kind, id, username string, perms []strin
return nil
}
func (client *Client) RequestConns(target group.Client, g *group.Group, id string) error {
func (client *Client) RequestConns(target hall.Client, g *hall.Hall, id string) error {
return nil
}
@@ -104,7 +104,7 @@ func (client *Client) Close() error {
func (client *Client) Kick(id string, user *string, message string) error {
err := client.Close()
group.DelClient(client)
hall.DelClient(client)
return err
}
@@ -116,8 +116,8 @@ func (client *Client) Joined(group, kind string) error {
return nil
}
func (client *Client) PushConn(g *group.Group, id string, up conn.Up, tracks []conn.UpTrack, replace string) error {
if client.group != g {
func (client *Client) PushConn(g *hall.Hall, id string, up conn.Up, tracks []conn.UpTrack, replace string) error {
if client.hall != g {
return nil
}
@@ -148,7 +148,7 @@ func (client *Client) PushConn(g *group.Group, id string, up conn.Up, tracks []c
return nil
}
directory := filepath.Join(Directory, client.group.Name())
directory := filepath.Join(Directory, client.hall.Name())
err := os.MkdirAll(directory, 0700)
if err != nil {
g.WallOps("Write to disk: " + err.Error())
@@ -192,7 +192,7 @@ func (conn *diskConn) warn(message string) {
return
}
log.Println(message)
conn.client.group.WallOps(message)
conn.client.hall.WallOps(message)
conn.lastWarning = now
}
@@ -330,7 +330,7 @@ func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []co
if audio == nil {
audio = remote
} else {
client.group.WallOps("Multiple audio tracks, recording just one")
client.hall.WallOps("Multiple audio tracks, recording just one")
}
} else if strings.EqualFold(codec, "video/vp8") ||
strings.EqualFold(codec, "video/vp9") ||
@@ -338,10 +338,10 @@ func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []co
if video == nil || video.Label() == "l" {
video = remote
} else if remote.Label() != "l" {
client.group.WallOps("Multiple video tracks, recording just one")
client.hall.WallOps("Multiple video tracks, recording just one")
}
} else {
client.group.WallOps("Unknown codec, " + codec + ", not recording")
client.hall.WallOps("Unknown codec, " + codec + ", not recording")
}
}
+6 -6
View File
@@ -1,4 +1,4 @@
package group
package hall
import (
"bytes"
@@ -134,7 +134,7 @@ type ClientCredentials struct {
}
type Client interface {
Group() *Group
Hall() *Hall
Addr() net.Addr
Id() string
Username() string
@@ -142,9 +142,9 @@ type Client interface {
Permissions() []string
SetPermissions([]string)
Data() map[string]interface{}
PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error
RequestConns(target Client, g *Group, id string) error
Joined(group, kind string) error
PushClient(group, kind, id, username string, perms []string, data map[string]interface{}) error
PushConn(g *Hall, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error
RequestConns(target Client, g *Hall, id string) error
Joined(hall, kind string) error
PushClient(hall, kind, id, username string, perms []string, data map[string]interface{}) error
Kick(id string, user *string, message string) error
}
+1 -1
View File
@@ -1,4 +1,4 @@
package group
package hall
import (
"bytes"
+39 -39
View File
@@ -1,4 +1,4 @@
package group
package hall
import (
"encoding/json"
@@ -147,11 +147,11 @@ func (u UserDescription) MarshalJSON() ([]byte, error) {
return json.Marshal(uu)
}
// Description represents a group description together with some metadata
// Description represents a hall description together with some metadata
// about the JSON file it was deserialised from.
type Description struct {
// The file this was deserialised from. This is not necessarily
// the name of the group, for example in case of a subgroup.
// the name of the hall, for example in case of a subgroup.
FileName string `json:"-"`
// The modtime and size of the file. These are used to detect
@@ -162,10 +162,10 @@ type Description struct {
// Whether this is an automatically generated subgroup
isSubgroup bool `json:"-"`
// The user-friendly group name
// The user-friendly hall name
DisplayName string `json:"displayName,omitempty"`
// A user-readable description of the group.
// A user-readable description of the hall.
Description string `json:"description,omitempty"`
// A user-readable contact, typically an e-mail address.
@@ -174,10 +174,10 @@ type Description struct {
// A user-readable comment. Ignored by the server.
Comment string `json:"comment,omitempty"`
// Whether to display the group on the landing page.
// Whether to display the hall on the landing page.
Public bool `json:"public,omitempty"`
// A URL to redirect the group to. If this is not empty, most
// A URL to redirect the hall to. If this is not empty, most
// other fields are ignored.
Redirect string `json:"redirect,omitempty"`
@@ -202,7 +202,7 @@ type Description struct {
// Whether subgroups are created on the fly.
AutoSubgroups bool `json:"auto-subgroups,omitempty"`
// Whether to lock the group when the last op logs out.
// Whether to lock the hall when the last op logs out.
Autolock bool `json:"autolock,omitempty"`
// Whether to kick all users when the last op logs out.
@@ -278,7 +278,7 @@ func descriptionMatch(d1, d2 *Description) bool {
return true
}
// descriptionUnchanged returns true if a group's description hasn't
// descriptionUnchanged returns true if a hall's description hasn't
// changed since it was last read.
func descriptionUnchanged(name string, desc *Description) bool {
fi, fileName, _, err := getDescriptionFile(name, true, os.Stat)
@@ -292,7 +292,7 @@ func descriptionUnchanged(name string, desc *Description) bool {
return true
}
// GetDescription gets a group description, either from cache or from disk
// GetDescription gets a hall description, either from cache or from disk
func GetDescription(name string) (*Description, error) {
g := Get(name)
if g != nil {
@@ -336,10 +336,10 @@ func makeETag(fileSize int64, modTime time.Time) string {
}
// DeleteDescription deletes a description (and therefore persistently
// deletes a group) but only if it matches a given ETag.
// deletes a hall) but only if it matches a given ETag.
func DeleteDescription(name, etag string) error {
groups.mu.Lock()
defer groups.mu.Unlock()
halls.mu.Lock()
defer halls.mu.Unlock()
fi, fileName, _, err := getDescriptionFile(name, false, os.Stat)
if err != nil {
@@ -352,14 +352,14 @@ func DeleteDescription(name, etag string) error {
}
// UpdateDescription overwrites a description if it matches a given ETag.
// In order to create a new group, pass an empty ETag.
// In order to create a new hall, pass an empty ETag.
func UpdateDescription(name, etag string, desc *Description) error {
if desc.Users != nil || desc.WildcardUser != nil || desc.AuthKeys != nil {
return errors.New("description is not sanitised")
}
groups.mu.Lock()
defer groups.mu.Unlock()
halls.mu.Lock()
defer halls.mu.Unlock()
oldetag := ""
var filename string
@@ -438,7 +438,7 @@ func rewriteDescriptionFile(filename string, desc *Description) error {
}
// readDescription reads a group's description from disk
// readDescription reads a hall's description from disk
func readDescription(name string, allowSubgroups bool) (*Description, error) {
r, fileName, isSubgroup, err :=
getDescriptionFile(name, allowSubgroups, os.Open)
@@ -585,7 +585,7 @@ func GetDescriptionNames() ([]string, error) {
return names, err
}
func SetKeys(group string, keys []map[string]any) error {
func SetKeys(hall string, keys []map[string]any) error {
if keys != nil {
_, err := token.ParseKeys(keys, "", "")
if err != nil {
@@ -593,10 +593,10 @@ func SetKeys(group string, keys []map[string]any) error {
}
}
groups.mu.Lock()
defer groups.mu.Unlock()
halls.mu.Lock()
defer halls.mu.Unlock()
desc, err := readDescription(group, false)
desc, err := readDescription(hall, false)
if err != nil {
return err
}
@@ -604,8 +604,8 @@ func SetKeys(group string, keys []map[string]any) error {
return rewriteDescriptionFile(desc.FileName, desc)
}
func GetUsers(group string) ([]string, string, error) {
desc, err := GetDescription(group)
func GetUsers(hall string) ([]string, string, error) {
desc, err := GetDescription(hall)
if err != nil {
return nil, "", err
}
@@ -618,13 +618,13 @@ func GetUsers(group string) ([]string, string, error) {
return users, makeETag(desc.fileSize, desc.modTime), nil
}
func GetSanitisedUser(group, username string, wildcard bool) (UserDescription, string, error) {
func GetSanitisedUser(hall, username string, wildcard bool) (UserDescription, string, error) {
if wildcard && username != "" {
return UserDescription{}, "",
errors.New("wildcard with username")
}
desc, err := GetDescription(group)
desc, err := GetDescription(hall)
if err != nil {
return UserDescription{}, "", err
}
@@ -651,20 +651,20 @@ func GetSanitisedUser(group, username string, wildcard bool) (UserDescription, s
return u, makeETag(desc.fileSize, desc.modTime), nil
}
func GetUserTag(group, username string, wildcard bool) (string, error) {
_, etag, err := GetSanitisedUser(group, username, wildcard)
func GetUserTag(hall, username string, wildcard bool) (string, error) {
_, etag, err := GetSanitisedUser(hall, username, wildcard)
return etag, err
}
func DeleteUser(group, username string, wildcard bool, etag string) error {
func DeleteUser(hall, username string, wildcard bool, etag string) error {
if wildcard && username != "" {
return errors.New("wildcard with username")
}
groups.mu.Lock()
defer groups.mu.Unlock()
halls.mu.Lock()
defer halls.mu.Unlock()
desc, err := readDescription(group, false)
desc, err := readDescription(hall, false)
if err != nil {
return err
}
@@ -697,7 +697,7 @@ func DeleteUser(group, username string, wildcard bool, etag string) error {
return rewriteDescriptionFile(desc.FileName, desc)
}
func UpdateUser(group, username string, wildcard bool, etag string, user *UserDescription) error {
func UpdateUser(hall, username string, wildcard bool, etag string, user *UserDescription) error {
if wildcard && username != "" {
return errors.New("wildcard with username")
}
@@ -705,10 +705,10 @@ func UpdateUser(group, username string, wildcard bool, etag string, user *UserDe
return errors.New("user description is not sanitised")
}
groups.mu.Lock()
defer groups.mu.Unlock()
halls.mu.Lock()
defer halls.mu.Unlock()
desc, err := readDescription(group, false)
desc, err := readDescription(hall, false)
if err != nil {
return err
}
@@ -749,15 +749,15 @@ func UpdateUser(group, username string, wildcard bool, etag string, user *UserDe
return rewriteDescriptionFile(desc.FileName, desc)
}
func SetUserPassword(group, username string, wildcard bool, pw Password) error {
func SetUserPassword(hall, username string, wildcard bool, pw Password) error {
if wildcard && username != "" {
return errors.New("wildcard with username")
}
groups.mu.Lock()
defer groups.mu.Unlock()
halls.mu.Lock()
defer halls.mu.Unlock()
desc, err := readDescription(group, false)
desc, err := readDescription(hall, false)
if err != nil {
return err
}
@@ -1,4 +1,4 @@
package group
package hall
import (
"encoding/json"
+88 -88
View File
@@ -1,4 +1,4 @@
package group
package hall
import (
"encoding/json"
@@ -98,7 +98,7 @@ const (
MaxBitrate = 1024 * 1024 * 1024
)
type Group struct {
type Hall struct {
name string
mu sync.Mutex
@@ -110,11 +110,11 @@ type Group struct {
data map[string]interface{}
}
func (g *Group) Name() string {
func (g *Hall) Name() string {
return g.name
}
func (g *Group) Locked() (bool, string) {
func (g *Hall) Locked() (bool, string) {
g.mu.Lock()
defer g.mu.Unlock()
if g.locked != nil {
@@ -124,7 +124,7 @@ func (g *Group) Locked() (bool, string) {
}
}
func (g *Group) SetLocked(locked bool, message string) {
func (g *Hall) SetLocked(locked bool, message string) {
g.mu.Lock()
if locked {
g.locked = &message
@@ -139,13 +139,13 @@ func (g *Group) SetLocked(locked bool, message string) {
}
}
func (g *Group) Data() map[string]interface{} {
func (g *Hall) Data() map[string]interface{} {
g.mu.Lock()
defer g.mu.Unlock()
return maps.Clone(g.data)
}
func (g *Group) UpdateData(d map[string]interface{}) {
func (g *Hall) UpdateData(d map[string]interface{}) {
g.mu.Lock()
if g.data == nil {
g.data = make(map[string]interface{})
@@ -165,19 +165,19 @@ func (g *Group) UpdateData(d map[string]interface{}) {
}
}
func (g *Group) Description() *Description {
func (g *Hall) Description() *Description {
g.mu.Lock()
defer g.mu.Unlock()
return g.description
}
func (g *Group) ClientCount() int {
func (g *Hall) ClientCount() int {
g.mu.Lock()
defer g.mu.Unlock()
return len(g.clients)
}
func (g *Group) mayExpire() bool {
func (g *Hall) mayExpire() bool {
g.mu.Lock()
defer g.mu.Unlock()
@@ -190,12 +190,12 @@ func (g *Group) mayExpire() bool {
return time.Since(g.timestamp) > maxHistoryAge(g.description)
}
var groups struct {
var halls struct {
mu sync.Mutex
groups map[string]*Group
halls map[string]*Hall
}
func (g *Group) API() (*webrtc.API, error) {
func (g *Hall) API() (*webrtc.API, error) {
g.mu.Lock()
codecs := g.description.Codecs
g.mu.Unlock()
@@ -432,7 +432,7 @@ func APIFromNames(names []string) (*webrtc.API, error) {
return APIFromCodecs(codecs)
}
func Add(name string, desc *Description) (*Group, error) {
func Add(name string, desc *Description) (*Hall, error) {
g, notify, err := add(name, desc)
for _, c := range notify {
c.Joined(g.Name(), "change")
@@ -440,7 +440,7 @@ func Add(name string, desc *Description) (*Group, error) {
return g, err
}
func validGroupName(name string) bool {
func validHallName(name string) bool {
if strings.ContainsRune(name, '\\') {
return false
}
@@ -458,21 +458,21 @@ func validGroupName(name string) bool {
return s == "/"+name
}
func add(name string, desc *Description) (*Group, []Client, error) {
if !validGroupName(name) {
return nil, nil, UserError("illegal group name")
func add(name string, desc *Description) (*Hall, []Client, error) {
if !validHallName(name) {
return nil, nil, UserError("illegal hall name")
}
groups.mu.Lock()
defer groups.mu.Unlock()
halls.mu.Lock()
defer halls.mu.Unlock()
if groups.groups == nil {
groups.groups = make(map[string]*Group)
if halls.halls == nil {
halls.halls = make(map[string]*Hall)
}
var err error
g := groups.groups[name]
g := halls.halls[name]
if g == nil {
if desc == nil {
desc, err = readDescription(name, true)
@@ -481,13 +481,13 @@ func add(name string, desc *Description) (*Group, []Client, error) {
}
}
g = &Group{
g = &Hall{
name: name,
description: desc,
clients: make(map[string]Client),
timestamp: time.Now(),
}
groups.groups[name] = g
halls.halls[name] = g
}
g.mu.Lock()
@@ -503,7 +503,7 @@ func add(name string, desc *Description) (*Group, []Client, error) {
desc, err = readDescription(name, true)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
log.Printf("Reading group %v: %v", name, err)
log.Printf("Reading hall %v: %v", name, err)
}
deleteUnlocked(g)
return nil, nil, err
@@ -521,11 +521,11 @@ func add(name string, desc *Description) (*Group, []Client, error) {
return g, clients, nil
}
func Range(f func(g *Group) bool) {
groups.mu.Lock()
defer groups.mu.Unlock()
func Range(f func(g *Hall) bool) {
halls.mu.Lock()
defer halls.mu.Unlock()
for _, g := range groups.groups {
for _, g := range halls.halls {
ok := f(g)
if !ok {
break
@@ -536,7 +536,7 @@ func Range(f func(g *Group) bool) {
func GetNames() []string {
names := make([]string, 0)
Range(func(g *Group) bool {
Range(func(g *Hall) bool {
names = append(names, g.name)
return true
})
@@ -552,7 +552,7 @@ func GetSubGroups(parent string) []SubGroup {
prefix := parent + "/"
subgroups := make([]SubGroup, 0)
Range(func(g *Group) bool {
Range(func(g *Hall) bool {
if strings.HasPrefix(g.name, prefix) {
g.mu.Lock()
count := len(g.clients)
@@ -567,19 +567,19 @@ func GetSubGroups(parent string) []SubGroup {
return subgroups
}
func Get(name string) *Group {
groups.mu.Lock()
defer groups.mu.Unlock()
if groups.groups == nil {
func Get(name string) *Hall {
halls.mu.Lock()
defer halls.mu.Unlock()
if halls.halls == nil {
return nil
}
return groups.groups[name]
return halls.halls[name]
}
func Delete(name string) bool {
groups.mu.Lock()
defer groups.mu.Unlock()
g := groups.groups[name]
halls.mu.Lock()
defer halls.mu.Unlock()
g := halls.halls[name]
if g == nil {
return false
}
@@ -589,13 +589,13 @@ func Delete(name string) bool {
return deleteUnlocked(g)
}
// Called with both groups.mu and g.mu taken.
func deleteUnlocked(g *Group) bool {
// Called with both halls.mu and g.mu taken.
func deleteUnlocked(g *Hall) bool {
if len(g.clients) != 0 {
return false
}
delete(groups.groups, g.name)
delete(halls.halls, g.name)
return true
}
@@ -608,8 +608,8 @@ func member(v string, l []string) bool {
return false
}
func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) {
g, err := Add(group, nil)
func AddClient(hall string, c Client, creds ClientCredentials) (*Hall, error) {
g, err := Add(hall, nil)
if err != nil {
return nil, err
}
@@ -632,7 +632,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error)
if g.locked != nil {
m := *g.locked
if m == "" {
m = "this group is locked"
m = "this hall is locked"
}
return nil, UserError(m)
}
@@ -642,13 +642,13 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error)
if g.description.NotBefore != nil &&
g.description.NotBefore.After(now) {
return nil, UserError(
"this group is not open yet",
"this hall is not open yet",
)
}
if g.description.Expires != nil &&
g.description.Expires.Before(now) {
return nil, UserError(
"this group is closed",
"this hall is closed",
)
}
}
@@ -663,7 +663,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error)
if !ops {
return nil, UserError(
"there are no operators " +
"in this group",
"in this hall",
)
}
}
@@ -702,7 +702,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error)
}
// called locked
func autoLockKick(g *Group) {
func autoLockKick(g *Hall) {
if !(g.description.Autolock && g.locked == nil) &&
!g.description.Autokick {
return
@@ -715,7 +715,7 @@ func autoLockKick(g *Group) {
}
}
if g.description.Autolock && g.locked == nil {
m := "this group is locked"
m := "this hall is locked"
g.locked = &m
for _, c := range clients {
c.Joined(g.Name(), "change")
@@ -723,14 +723,14 @@ func autoLockKick(g *Group) {
}
if g.description.Autokick {
// we cannot call kickall, since it requires the group to
// we cannot call kickall, since it requires the hall to
// be unlocked. And calling it asynchronously might
// spuriously kick out an operator.
go func(clients []Client) {
for _, c := range clients {
c.Kick(
"", nil,
"there are no operators in this group",
"there are no operators in this hall",
)
}
}(g.getClientsUnlocked(nil))
@@ -738,7 +738,7 @@ func autoLockKick(g *Group) {
}
func DelClient(c Client) {
g := c.Group()
g := c.Hall()
if g == nil {
return
}
@@ -762,13 +762,13 @@ func DelClient(c Client) {
autoLockKick(g)
}
func (g *Group) GetClients(except Client) []Client {
func (g *Hall) GetClients(except Client) []Client {
g.mu.Lock()
defer g.mu.Unlock()
return g.getClientsUnlocked(except)
}
func (g *Group) getClientsUnlocked(except Client) []Client {
func (g *Hall) getClientsUnlocked(except Client) []Client {
clients := make([]Client, 0, len(g.clients))
for _, c := range g.clients {
if c != except {
@@ -778,13 +778,13 @@ func (g *Group) getClientsUnlocked(except Client) []Client {
return clients
}
func (g *Group) GetClient(id string) Client {
func (g *Hall) GetClient(id string) Client {
g.mu.Lock()
defer g.mu.Unlock()
return g.getClientUnlocked(id)
}
func (g *Group) getClientUnlocked(id string) Client {
func (g *Hall) getClientUnlocked(id string) Client {
for idd, c := range g.clients {
if idd == id {
return c
@@ -793,7 +793,7 @@ func (g *Group) getClientUnlocked(id string) Client {
return nil
}
func (g *Group) Range(f func(c Client) bool) {
func (g *Hall) Range(f func(c Client) bool) {
g.mu.Lock()
defer g.mu.Unlock()
for _, c := range g.clients {
@@ -804,7 +804,7 @@ func (g *Group) Range(f func(c Client) bool) {
}
}
func kickall(g *Group, message string) {
func kickall(g *Hall, message string) {
g.Range(func(c Client) bool {
c.Kick("", nil, message)
return true
@@ -812,7 +812,7 @@ func kickall(g *Group, message string) {
}
func Shutdown(message string) {
Range(func(g *Group) bool {
Range(func(g *Hall) bool {
g.SetLocked(true, message)
kickall(g, message)
return true
@@ -823,7 +823,7 @@ type warner interface {
Warn(oponly bool, message string) error
}
func (g *Group) WallOps(message string) {
func (g *Hall) WallOps(message string) {
clients := g.GetClients(nil)
for _, c := range clients {
w, ok := c.(warner)
@@ -865,7 +865,7 @@ func deleteFunc[S ~[]E, E any](s S, f func(E) bool) S {
return s[:i]
}
func (g *Group) ClearChatHistory(id string, userId string) {
func (g *Hall) ClearChatHistory(id string, userId string) {
g.mu.Lock()
defer g.mu.Unlock()
if id == "" && userId == "" {
@@ -877,7 +877,7 @@ func (g *Group) ClearChatHistory(id string, userId string) {
})
}
func (g *Group) AddToChatHistory(id, source string, user *string, time time.Time, kind string, value interface{}) {
func (g *Hall) AddToChatHistory(id, source string, user *string, time time.Time, kind string, value interface{}) {
g.mu.Lock()
defer g.mu.Unlock()
@@ -905,7 +905,7 @@ func discardObsoleteHistory(h []ChatHistoryEntry, duration time.Duration) []Chat
return h
}
func (g *Group) GetChatHistory() []ChatHistoryEntry {
func (g *Hall) GetChatHistory() []ChatHistoryEntry {
g.mu.Lock()
defer g.mu.Unlock()
@@ -993,7 +993,7 @@ func GetConfiguration() (*Configuration, error) {
}
// called locked
func (g *Group) getPasswordPermission(creds ClientCredentials) (Permissions, error) {
func (g *Hall) getPasswordPermission(creds ClientCredentials) (Permissions, error) {
desc := g.description
if creds.Username == nil {
@@ -1024,14 +1024,14 @@ func (g *Group) getPasswordPermission(creds ClientCredentials) (Permissions, err
// Return true if there is a user entry with the given username.
// Always return false for an empty username.
func (g *Group) UserExists(username string) bool {
func (g *Hall) UserExists(username string) bool {
g.mu.Lock()
defer g.mu.Unlock()
return g.userExists(username)
}
// called locked
func (g *Group) userExists(username string) bool {
func (g *Hall) userExists(username string) bool {
desc := g.description
if desc.Users == nil {
return false
@@ -1046,11 +1046,11 @@ func (g *Group) userExists(username string) bool {
// usernames might lead to security vulnaribilities.
// For now, we just do the minimal validation that avoids path traversal.
func validUsername(username string) bool {
return username == "" || validGroupName(username)
return username == "" || validHallName(username)
}
// called locked
func (g *Group) getPermission(creds ClientCredentials) (string, []string, error) {
func (g *Hall) getPermission(creds ClientCredentials) (string, []string, error) {
desc := g.description
var username string
var perms []string
@@ -1096,7 +1096,7 @@ func (g *Group) getPermission(creds ClientCredentials) (string, []string, error)
return username, perms, nil
}
func (g *Group) GetPermission(creds ClientCredentials) (string, []string, error) {
func (g *Hall) GetPermission(creds ClientCredentials) (string, []string, error) {
g.mu.Lock()
defer g.mu.Unlock()
return g.getPermission(creds)
@@ -1116,10 +1116,10 @@ type Status struct {
CanChangePassword bool `json:"canChangePassword,omitempty"`
}
// Status returns a group's status.
// Base is the base URL for groups; if omitted, then both the Location and
// Status returns a hall's status.
// Base is the base URL for halls; if omitted, then both the Location and
// Endpoint members are omitted from the result.
func (g *Group) Status(authentified bool, base *url.URL) Status {
func (g *Hall) Status(authentified bool, base *url.URL) Status {
desc := g.Description()
if desc.Redirect != "" {
@@ -1141,7 +1141,7 @@ func (g *Group) Status(authentified bool, base *url.URL) Status {
Scheme: base.Scheme,
Host: base.Host,
Path: path.Join(
path.Join(base.Path, "/group/"),
path.Join(base.Path, "/hall/"),
g.Name()) + "/",
}
location = l.String()
@@ -1181,7 +1181,7 @@ func (g *Group) Status(authentified bool, base *url.URL) Status {
func GetPublic(base *url.URL) []Status {
gs := make([]Status, 0)
Range(func(g *Group) bool {
Range(func(g *Hall) bool {
if g.Description().Public {
gs = append(gs, g.Status(false, base))
}
@@ -1193,9 +1193,9 @@ func GetPublic(base *url.URL) []Status {
return gs
}
// Update checks that all in-memory groups are up-to-date and updates the
// list of public groups. It also removes from memory any non-public
// groups that haven't been accessed in maxHistoryAge.
// Update checks that all in-memory halls are up-to-date and updates the
// list of public halls. It also removes from memory any non-public
// halls that haven't been accessed in maxHistoryAge.
func Update() {
_, err := GetConfiguration()
if err != nil {
@@ -1214,11 +1214,11 @@ func Update() {
deleted := false
if g.mayExpire() {
// Delete checks if the group is still empty
// Delete checks if the hall is still empty
deleted = Delete(name)
}
// update group description
// update hall description
if !deleted {
Add(name, nil)
}
@@ -1228,14 +1228,14 @@ func Update() {
Directory,
func(path string, d fs.DirEntry, err error) error {
if err != nil {
log.Printf("Group file %v: %v", path, err)
log.Printf("Hall file %v: %v", path, err)
return nil
}
if d.IsDir() {
base := filepath.Base(path)
if base[0] == '.' {
log.Printf(
"Ignoring group directory %v",
"Ignoring hall directory %v",
path,
)
return fs.SkipDir
@@ -1244,25 +1244,25 @@ func Update() {
}
filename, err := filepath.Rel(Directory, path)
if err != nil {
log.Printf("Group file %v: %v", path, err)
log.Printf("Hall file %v: %v", path, err)
return nil
}
if !strings.HasSuffix(filename, ".json") {
log.Printf(
"Unexpected extension for group file %v",
"Unexpected extension for hall file %v",
path,
)
return nil
}
base := filepath.Base(filename)
if base[0] == '.' {
log.Printf("Ignoring group file %v", filename)
log.Printf("Ignoring hall file %v", filename)
return nil
}
name := strings.TrimSuffix(filename, ".json")
desc, err := GetDescription(name)
if err != nil {
log.Printf("Group file %v: %v", path, err)
log.Printf("Hall file %v: %v", path, err)
return nil
}
if desc.Public {
@@ -1273,6 +1273,6 @@ func Update() {
)
if err != nil {
log.Printf("Couldn't read groups: %v", err)
log.Printf("Couldn't read halls: %v", err)
}
}
+19 -19
View File
@@ -1,4 +1,4 @@
package group
package hall
import (
"encoding/json"
@@ -35,21 +35,21 @@ func TestConstantTimeCompare(t *testing.T) {
}
func TestGroup(t *testing.T) {
groups.groups = nil
Add("group", &Description{})
Add("group/subgroup", &Description{Public: true})
if len(groups.groups) != 2 {
t.Errorf("Expected 2, got %v", len(groups.groups))
halls.halls = nil
Add("hall", &Description{})
Add("hall/subgroup", &Description{Public: true})
if len(halls.halls) != 2 {
t.Errorf("Expected 2, got %v", len(halls.halls))
}
g := Get("group")
g2 := Get("group/subgroup")
g := Get("hall")
g2 := Get("hall/subgroup")
if g == nil {
t.Fatalf("Couldn't get group")
t.Fatalf("Couldn't get hall")
}
if g2 == nil {
t.Fatalf("Couldn't get group/subgroup")
t.Fatalf("Couldn't get hall/subgroup")
}
if name := g.Name(); name != "group" {
if name := g.Name(); name != "hall" {
t.Errorf("Name: expected group1, got %v", name)
}
if locked, _ := g.Locked(); locked {
@@ -64,17 +64,17 @@ func TestGroup(t *testing.T) {
t.Errorf("Expected 2, got %v", names)
}
if subs := GetSubGroups("group"); len(subs) != 0 {
if subs := GetSubGroups("hall"); len(subs) != 0 {
t.Errorf("Expected [], got %v", subs)
}
if public := GetPublic(nil); len(public) != 1 || public[0].Name != "group/subgroup" {
t.Errorf("Expected group/subgroup, got %v", public)
if public := GetPublic(nil); len(public) != 1 || public[0].Name != "hall/subgroup" {
t.Errorf("Expected hall/subgroup, got %v", public)
}
}
func TestChatHistory(t *testing.T) {
g := Group{
g := Hall{
description: &Description{},
}
user := "user"
@@ -213,7 +213,7 @@ var goodClients = []credPerm{
}
func TestPermissions(t *testing.T) {
var g Group
var g Hall
err := json.Unmarshal([]byte(desc2JSON), &g.description)
if err != nil {
t.Fatalf("unmarshal: %v", err)
@@ -299,7 +299,7 @@ func TestExtraPermissions(t *testing.T) {
}
func TestUsernameTaken(t *testing.T) {
var g Group
var g Hall
err := json.Unmarshal([]byte(desc2JSON), &g.description)
if err != nil {
t.Fatalf("unmarshal: %v", err)
@@ -345,7 +345,7 @@ func TestFmtpValue(t *testing.T) {
}
}
func TestValidGroupName(t *testing.T) {
func TestValidHallName(t *testing.T) {
type nameTest struct {
name string
result bool
@@ -367,7 +367,7 @@ func TestValidGroupName(t *testing.T) {
}
for _, test := range tests {
r := validGroupName(test.name)
r := validHallName(test.name)
if r != test.result {
t.Errorf("Valid %v: got %v, expected %v",
test.name, r, test.result)
+17 -17
View File
@@ -17,7 +17,7 @@ import (
"git.stormux.org/storm/skald/codecs"
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/estimator"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/ice"
"git.stormux.org/storm/skald/jitter"
"git.stormux.org/storm/skald/packetcache"
@@ -193,8 +193,8 @@ func (down *rtpDownConnection) getTracks() []*rtpDownTrack {
return tracks
}
func newDownConn(c group.Client, id string, remote conn.Up) (*rtpDownConnection, error) {
api, err := c.Group().API()
func newDownConn(c hall.Client, id string, remote conn.Up) (*rtpDownConnection, error) {
api, err := c.Hall().API()
if err != nil {
return nil, err
}
@@ -504,7 +504,7 @@ func (up *rtpUpTrack) hasRtcpFb(tpe, parameter string) bool {
type rtpUpConnection struct {
id string
client group.Client
client hall.Client
label string
pc *webrtc.PeerConnection
iceCandidates []*webrtc.ICECandidateInit
@@ -599,7 +599,7 @@ func (up *rtpUpConnection) flushICECandidates() error {
}
// pushConnNow pushes a connection to all of the clients in a group
func pushConnNow(up *rtpUpConnection, g *group.Group, cs []group.Client) {
func pushConnNow(up *rtpUpConnection, g *hall.Hall, cs []hall.Client) {
up.mu.Lock()
up.pushed = true
replace := up.replace
@@ -616,12 +616,12 @@ func pushConnNow(up *rtpUpConnection, g *group.Group, cs []group.Client) {
}
// pushConn schedules a call to pushConnNow
func pushConn(up *rtpUpConnection, g *group.Group, cs []group.Client) {
func pushConn(up *rtpUpConnection, g *hall.Hall, cs []hall.Client) {
up.mu.Lock()
up.pushed = false
up.mu.Unlock()
go func(g *group.Group, cs []group.Client) {
go func(g *hall.Hall, cs []hall.Client) {
time.Sleep(200 * time.Millisecond)
up.mu.Lock()
pushed := up.pushed
@@ -633,14 +633,14 @@ func pushConn(up *rtpUpConnection, g *group.Group, cs []group.Client) {
}(g, cs)
}
func newUpConn(c group.Client, id string, label string, offer string) (*rtpUpConnection, error) {
func newUpConn(c hall.Client, id string, label string, offer string) (*rtpUpConnection, error) {
var o sdp.SessionDescription
err := o.Unmarshal([]byte(offer))
if err != nil {
return nil, err
}
api, err := c.Group().API()
api, err := c.Hall().API()
if err != nil {
return nil, err
}
@@ -686,10 +686,10 @@ func newUpConn(c group.Client, id string, label string, offer string) (*rtpUpCon
up.mu.Unlock()
pushConn(up, c.Group(), c.Group().GetClients(c))
pushConn(up, c.Hall(), c.Hall().GetClients(c))
})
pushConn(up, c.Group(), c.Group().GetClients(c))
pushConn(up, c.Hall(), c.Hall().GetClients(c))
go rtcpUpSender(up)
return up, nil
@@ -890,7 +890,7 @@ func sadd(x, y uint64) uint64 {
func maxUpBitrate(t *rtpUpTrack) uint64 {
minrate := ^uint64(0)
maxrate := uint64(group.MinBitrate)
maxrate := uint64(hall.MinBitrate)
maxsid := 0
maxtid := 0
local := t.getLocal()
@@ -902,8 +902,8 @@ func maxUpBitrate(t *rtpUpTrack) uint64 {
if maxtid < tid {
maxtid = tid
}
if r < group.MinBitrate {
r = group.MinBitrate
if r < hall.MinBitrate {
r = hall.MinBitrate
}
if minrate > r {
minrate = r
@@ -997,14 +997,14 @@ func sendUpRTCP(up *rtpUpConnection) error {
if t.Kind() == webrtc.RTPCodecTypeAudio {
rate = sadd(rate, 100*1024)
} else if t.Label() == "l" {
rate = sadd(rate, group.LowBitrate)
rate = sadd(rate, hall.LowBitrate)
} else {
rate = sadd(rate, maxUpBitrate(t))
}
}
if rate > group.MaxBitrate {
rate = group.MaxBitrate
if rate > hall.MaxBitrate {
rate = hall.MaxBitrate
}
if len(ssrcs) > 0 {
packets = append(packets,
+103 -103
View File
@@ -20,7 +20,7 @@ import (
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/diskwriter"
"git.stormux.org/storm/skald/estimator"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/ice"
"git.stormux.org/storm/skald/token"
"git.stormux.org/storm/skald/unbounded"
@@ -33,7 +33,7 @@ func errorToWSCloseMessage(id string, err error) (*clientMessage, []byte) {
switch e := err.(type) {
case *websocket.CloseError:
code = websocket.CloseNormalClosure
case group.ProtocolError:
case hall.ProtocolError:
code = websocket.CloseProtocolError
m = &clientMessage{
Type: "usermessage",
@@ -43,7 +43,7 @@ func errorToWSCloseMessage(id string, err error) (*clientMessage, []byte) {
Value: e.Error(),
}
text = e.Error()
case group.UserError, group.KickError:
case hall.UserError, hall.KickError:
code = websocket.CloseNormalClosure
m = errorMessage(id, err)
text = e.Error()
@@ -60,7 +60,7 @@ func isWSNormalError(err error) bool {
}
type webClient struct {
group *group.Group
hall *hall.Hall
addr net.Addr
id string
username string
@@ -77,8 +77,8 @@ type webClient struct {
up map[string]*rtpUpConnection
}
func (c *webClient) Group() *group.Group {
return c.group
func (c *webClient) Hall() *hall.Hall {
return c.hall
}
func (c *webClient) Addr() net.Addr {
@@ -130,7 +130,7 @@ type clientMessage struct {
Token string `json:"token,omitempty"`
Privileged bool `json:"privileged,omitempty"`
Permissions []string `json:"permissions,omitempty"`
Status *group.Status `json:"status,omitempty"`
Status *hall.Status `json:"status,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
Group string `json:"group,omitempty"`
Value interface{} `json:"value,omitempty"`
@@ -229,7 +229,7 @@ func delUpConn(c *webClient, id string, userId string, push bool) error {
replace := conn.getReplace(false)
delete(c.up, id)
g := c.group
g := c.hall
c.mu.Unlock()
conn.mu.Lock()
@@ -386,9 +386,9 @@ func addDownTrackUnlocked(conn *rtpDownConnection, remoteTrack *rtpUpTrack) erro
// replace the RTCP feedback types with the ones we understand
remoteCodec := remoteTrack.Codec()
if strings.HasPrefix(strings.ToLower(remoteCodec.MimeType), "video/") {
remoteCodec.RTCPFeedback = group.VideoRTCPFeedback
remoteCodec.RTCPFeedback = hall.VideoRTCPFeedback
} else {
remoteCodec.RTCPFeedback = group.AudioRTCPFeedback
remoteCodec.RTCPFeedback = hall.AudioRTCPFeedback
}
local, err := webrtc.NewTrackLocalStaticRTP(
@@ -408,7 +408,7 @@ func addDownTrackUnlocked(conn *rtpDownConnection, remoteTrack *rtpUpTrack) erro
}
codec := local.Codec()
ptype, err := group.CodecPayloadType(local.Codec())
ptype, err := hall.CodecPayloadType(local.Codec())
if err != nil {
log.Printf("Couldn't determine ptype for codec %v: %v",
codec.MimeType, err)
@@ -726,31 +726,31 @@ func parseRequested(r interface{}) (map[string][]string, error) {
}
func (c *webClient) setRequested(requested map[string][]string) error {
if c.group == nil {
if c.hall == nil {
return errors.New("attempted to request with no group joined")
}
c.requested = requested
requestConns(c, c.group, "")
requestConns(c, c.hall, "")
return nil
}
func (c *webClient) setRequestedStream(down *rtpDownConnection, requested []string) error {
var remoteClient group.Client
var remoteClient hall.Client
remote, ok := down.remote.(*rtpUpConnection)
if ok {
remoteClient = remote.client
}
down.requested = requested
return remoteClient.RequestConns(c, c.group, remote.id)
return remoteClient.RequestConns(c, c.hall, remote.id)
}
func (c *webClient) RequestConns(target group.Client, g *group.Group, id string) error {
func (c *webClient) RequestConns(target hall.Client, g *hall.Hall, id string) error {
c.action(requestConnsAction{g, target, id})
return nil
}
func requestConns(target group.Client, g *group.Group, id string) {
func requestConns(target hall.Client, g *hall.Hall, id string) {
clients := g.GetClients(target)
for _, c := range clients {
c.RequestConns(target, g, id)
@@ -817,7 +817,7 @@ func requestedTracks(c *webClient, requested []string, tracks []conn.UpTrack) ([
return ts, limitSid
}
func (c *webClient) PushConn(g *group.Group, id string, up conn.Up, tracks []conn.UpTrack, replace string) error {
func (c *webClient) PushConn(g *hall.Hall, id string, up conn.Up, tracks []conn.UpTrack, replace string) error {
c.action(pushConnAction{g, id, up, tracks, replace})
return nil
}
@@ -854,7 +854,7 @@ func StartClient(conn *websocket.Conn, addr net.Addr) (err error) {
),
)
conn.Close()
err = group.ProtocolError("client didn't handshake")
err = hall.ProtocolError("client didn't handshake")
return
}
@@ -883,7 +883,7 @@ func StartClient(conn *websocket.Conn, addr net.Addr) (err error) {
m, e := errorToWSCloseMessage(c.id, err)
if isWSNormalError(err) {
err = nil
} else if _, ok := err.(group.KickError); ok {
} else if _, ok := err.(hall.KickError); ok {
err = nil
}
if m != nil {
@@ -896,7 +896,7 @@ func StartClient(conn *websocket.Conn, addr net.Addr) (err error) {
}
type pushConnAction struct {
group *group.Group
hall *hall.Hall
id string
conn conn.Up
tracks []conn.UpTrack
@@ -904,8 +904,8 @@ type pushConnAction struct {
}
type requestConnsAction struct {
group *group.Group
target group.Client
hall *hall.Hall
target hall.Client
id string
}
@@ -939,7 +939,7 @@ type kickAction struct {
message string
}
var errEmptyId = group.ProtocolError("empty id")
var errEmptyId = hall.ProtocolError("empty id")
func member(v string, l []string) bool {
for _, w := range l {
@@ -1108,14 +1108,14 @@ func pushDownConn(c *webClient, id string, up conn.Up, tracks []conn.UpTrack, re
func handleAction(c *webClient, a any) error {
switch a := a.(type) {
case pushConnAction:
if c.group == nil || c.group != a.group {
if c.hall == nil || c.hall != a.hall {
log.Printf("Got connectsions for wrong group")
return nil
}
return pushDownConn(c, a.id, a.conn, a.tracks, a.replace)
case requestConnsAction:
g := c.group
if g == nil || a.group != g {
g := c.hall
if g == nil || a.hall != g {
log.Printf("Misdirected pushConns")
return nil
}
@@ -1148,7 +1148,7 @@ func handleAction(c *webClient, a any) error {
tracks[i] = t.remote
}
c.PushConn(
c.group,
c.hall,
down.remote.Id(), down.remote,
tracks, "",
)
@@ -1163,8 +1163,8 @@ func handleAction(c *webClient, a any) error {
}
case pushClientAction:
if a.group != c.group.Name() {
log.Printf("got client for wrong group")
if a.group != c.hall.Name() {
log.Printf("got client for wrong hall")
return nil
}
perms := append([]string(nil), a.permissions...)
@@ -1178,11 +1178,11 @@ func handleAction(c *webClient, a any) error {
Data: a.data,
})
case joinedAction:
var status *group.Status
var status *hall.Status
var data map[string]interface{}
var g *group.Group
var g *hall.Hall
if a.group != "" {
g = group.Get(a.group)
g = hall.Get(a.group)
if g != nil {
s := g.Status(true, nil)
status = &s
@@ -1230,7 +1230,7 @@ func handleAction(c *webClient, a any) error {
switch a.kind {
case "op":
c.permissions = addnew("op", c.permissions)
g := c.Group()
g := c.Hall()
if g != nil && g.Description().AllowRecording {
c.permissions = addnew("record", c.permissions)
}
@@ -1246,11 +1246,11 @@ func handleAction(c *webClient, a any) error {
case "unshutup":
c.permissions = addnew("message", c.permissions)
default:
return group.UserError("unknown permission")
return hall.UserError("unknown permission")
}
c.action(permissionsChangedAction{})
case permissionsChangedAction:
g := c.Group()
g := c.Hall()
if g == nil {
return errors.New("Permissions changed in no group")
}
@@ -1284,7 +1284,7 @@ func handleAction(c *webClient, a any) error {
user := c.Username()
d := c.Data()
clients := g.GetClients(nil)
go func(clients []group.Client) {
go func(clients []hall.Client) {
for _, cc := range clients {
cc.PushClient(
g.Name(), "change", id, user, perms, d,
@@ -1292,7 +1292,7 @@ func handleAction(c *webClient, a any) error {
}
}(clients)
case kickAction:
return group.KickError{
return hall.KickError{
a.id, a.username, a.message,
}
default:
@@ -1313,7 +1313,7 @@ func failUpConnection(c *webClient, id string, message string) error {
}
}
if message != "" {
err := c.error(group.UserError(message))
err := c.error(hall.UserError(message))
if err != nil {
return err
}
@@ -1322,7 +1322,7 @@ func failUpConnection(c *webClient, id string, message string) error {
}
func leaveGroup(c *webClient) {
if c.group == nil {
if c.hall == nil {
return
}
@@ -1337,11 +1337,11 @@ func leaveGroup(c *webClient) {
}
}
group.DelClient(c)
hall.DelClient(c)
c.permissions = nil
c.data = nil
c.requested = make(map[string][]string)
c.group = nil
c.hall = nil
}
func closeDownConn(c *webClient, id string, message string) error {
@@ -1357,7 +1357,7 @@ func closeDownConn(c *webClient, id string, message string) error {
return err
}
if message != "" {
err := c.error(group.UserError(message))
err := c.error(hall.UserError(message))
if err != nil {
return err
}
@@ -1375,10 +1375,10 @@ func (c *webClient) Joined(group, kind string) error {
return nil
}
func kickClient(g *group.Group, id string, user *string, dest string, message string) error {
func kickClient(g *hall.Hall, id string, user *string, dest string, message string) error {
client := g.GetClient(dest)
if client == nil {
return group.UserError("no such user")
return hall.UserError("no such user")
}
return client.Kick(id, user, message)
@@ -1387,14 +1387,14 @@ func kickClient(g *group.Group, id string, user *string, dest string, message st
func handleClientMessage(c *webClient, m clientMessage) error {
if m.Source != "" {
if m.Source != c.Id() {
return group.ProtocolError("spoofed client id")
return hall.ProtocolError("spoofed client id")
}
}
if m.Type != "join" {
if m.Username != nil {
if *m.Username != c.Username() {
return group.ProtocolError("spoofed username")
return hall.ProtocolError("spoofed username")
}
}
}
@@ -1402,25 +1402,25 @@ func handleClientMessage(c *webClient, m clientMessage) error {
switch m.Type {
case "join":
if m.Kind == "leave" {
if c.group == nil || c.group.Name() != m.Group {
return group.UserError("you are not joined")
if c.hall == nil || c.hall.Name() != m.Group {
return hall.UserError("you are not joined")
}
leaveGroup(c)
return nil
}
if m.Kind != "join" {
return group.ProtocolError("unknown kind")
return hall.ProtocolError("unknown kind")
}
if c.group != nil {
return group.ProtocolError(
if c.hall != nil {
return hall.ProtocolError(
"cannot join multiple groups",
)
}
c.data = m.Data
g, err := group.AddClient(m.Group, c,
group.ClientCredentials{
g, err := hall.AddClient(m.Group, c,
hall.ClientCredentials{
Username: m.Username,
Password: m.Password,
Token: m.Token,
@@ -1428,11 +1428,11 @@ func handleClientMessage(c *webClient, m clientMessage) error {
)
if err != nil {
var e, s string
var autherr *group.NotAuthorisedError
var autherr *hall.NotAuthorisedError
if errors.Is(err, token.ErrUsernameRequired) {
s = err.Error()
e = "need-username"
} else if errors.Is(err, group.ErrDuplicateUsername) {
} else if errors.Is(err, hall.ErrDuplicateUsername) {
s = err.Error()
e = "duplicate-username"
} else if errors.As(err, &autherr) {
@@ -1441,7 +1441,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
log.Printf("Join group: %v", err)
} else if errors.Is(err, os.ErrNotExist) {
s = "group does not exist"
} else if _, ok := err.(group.UserError); ok {
} else if _, ok := err.(hall.UserError); ok {
s = err.Error()
} else {
s = "internal server error"
@@ -1469,7 +1469,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
Value: redirect,
})
}
c.group = g
c.hall = g
case "request":
requested, err := parseRequested(m.Request)
if err != nil {
@@ -1498,7 +1498,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
Type: "abort",
Id: m.Id,
})
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
err := gotOffer(c, m.Id, m.Label, m.SDP, m.Replace)
if err != nil {
@@ -1565,16 +1565,16 @@ func handleClientMessage(c *webClient, m clientMessage) error {
return errEmptyId
}
if m.Candidate == nil {
return group.ProtocolError("null candidate")
return hall.ProtocolError("null candidate")
}
err := gotICE(c, m.Candidate, m.Id)
if err != nil {
log.Printf("ICE: %v", err)
}
case "chat", "usermessage":
g := c.group
g := c.hall
if g == nil {
return c.error(group.UserError("join a group first"))
return c.error(hall.UserError("join a group first"))
}
required := "message"
@@ -1582,7 +1582,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
required = "caption"
}
if !member(required, c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
id := m.Id
@@ -1614,7 +1614,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
Value: m.Value,
}
if m.Dest == "" {
var except group.Client
var except hall.Client
if m.NoEcho {
except = c
}
@@ -1625,38 +1625,38 @@ func handleClientMessage(c *webClient, m clientMessage) error {
} else {
cc := g.GetClient(m.Dest)
if cc == nil {
return c.error(group.UserError("user unknown"))
return c.error(hall.UserError("user unknown"))
}
ccc, ok := cc.(*webClient)
if !ok {
return c.error(group.UserError(
return c.error(hall.UserError(
"this user doesn't chat",
))
}
ccc.write(mm)
}
case "groupaction":
g := c.group
g := c.hall
if g == nil {
return c.error(group.UserError("join a group first"))
return c.error(hall.UserError("join a group first"))
}
switch m.Kind {
case "clearchat":
if !member("op", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
var id, userId string
if m.Value != nil {
value, ok := m.Value.(map[string]any)
if !ok {
return c.error(group.UserError(
return c.error(hall.UserError(
"bad value in clearchat",
))
}
id, _ = value["id"].(string)
userId, _ = value["userId"].(string)
if userId == "" && id != "" {
return c.error(group.UserError(
return c.error(hall.UserError(
"bad value in clearchat",
))
}
@@ -1674,7 +1674,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
}
case "lock", "unlock":
if !member("op", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
message := ""
v, ok := m.Value.(string)
@@ -1684,17 +1684,17 @@ func handleClientMessage(c *webClient, m clientMessage) error {
g.SetLocked(m.Kind == "lock", message)
case "record":
if !member("record", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
for _, cc := range g.GetClients(c) {
_, ok := cc.(*diskwriter.Client)
if ok {
return c.error(group.UserError("already recording"))
return c.error(hall.UserError("already recording"))
}
}
disk := diskwriter.New(g)
_, err := group.AddClient(g.Name(), disk,
group.ClientCredentials{
_, err := hall.AddClient(g.Name(), disk,
hall.ClientCredentials{
System: true,
},
)
@@ -1702,24 +1702,24 @@ func handleClientMessage(c *webClient, m clientMessage) error {
disk.Close()
return c.error(err)
}
requestConns(disk, c.group, "")
requestConns(disk, c.hall, "")
case "unrecord":
if !member("record", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
for _, cc := range g.GetClients(c) {
disk, ok := cc.(*diskwriter.Client)
if ok {
disk.Close()
group.DelClient(disk)
hall.DelClient(disk)
}
}
case "subgroups":
if !member("op", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
s := ""
for _, sg := range group.GetSubGroups(g.Name()) {
for _, sg := range hall.GetSubGroups(g.Name()) {
plural := ""
if sg.Clients > 1 {
plural = "s"
@@ -1737,11 +1737,11 @@ func handleClientMessage(c *webClient, m clientMessage) error {
})
case "setdata":
if !member("op", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
data, ok := m.Value.(map[string]interface{})
if !ok {
return c.error(group.UserError(
return c.error(hall.UserError(
"Bad value in setdata",
))
}
@@ -1773,7 +1773,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
return terror("error", "client specified token")
}
if tok.Group != c.group.Name() {
if tok.Group != c.hall.Name() {
return terror("error", "wrong group in token")
}
if tok.IncludeSubgroups {
@@ -1786,7 +1786,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
}
if tok.Username != nil &&
c.group.UserExists(*tok.Username) {
c.hall.UserExists(*tok.Username) {
return terror("error", "that username is taken")
}
@@ -1881,7 +1881,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
!member("token", c.permissions) {
return terror("not-authorised", "not authorised")
}
tokens, _, err := token.List(c.group.Name())
tokens, _, err := token.List(c.hall.Name())
if err != nil {
return terror("error", err.Error())
}
@@ -1892,37 +1892,37 @@ func handleClientMessage(c *webClient, m clientMessage) error {
Value: tokens,
})
default:
return group.UserError("unknown group action")
return hall.UserError("unknown group action")
}
case "useraction":
g := c.group
g := c.hall
if g == nil {
return c.error(group.UserError("join a group first"))
return c.error(hall.UserError("join a group first"))
}
switch m.Kind {
case "op", "unop", "present", "unpresent", "shutup", "unshutup":
if !member("op", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
t := g.GetClient(m.Dest)
if t == nil {
return c.error(group.UserError("no such user"))
return c.error(hall.UserError("no such user"))
}
target, ok := t.(*webClient)
if !ok {
return c.error(group.UserError(
return c.error(hall.UserError(
"this is not a real user",
))
}
target.action(changePermissionsAction{m.Kind})
case "identify":
if !member("op", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
d := g.GetClient(m.Dest)
if d == nil {
return c.error(
group.UserError("client not found"),
hall.UserError("client not found"),
)
}
value := make(map[string]any)
@@ -1950,7 +1950,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
})
case "kick":
if !member("op", c.permissions) {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
message := ""
v, ok := m.Value.(string)
@@ -1963,11 +1963,11 @@ func handleClientMessage(c *webClient, m clientMessage) error {
}
case "setdata":
if m.Dest != c.Id() {
return c.error(group.UserError("not authorised"))
return c.error(hall.UserError("not authorised"))
}
data, ok := m.Value.(map[string]interface{})
if !ok {
return c.error(group.UserError(
return c.error(hall.UserError(
"Bad value in setdata",
))
}
@@ -1985,7 +1985,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
user := c.Username()
perms := c.Permissions()
data = c.Data()
go func(clients []group.Client) {
go func(clients []hall.Client) {
for _, cc := range clients {
cc.PushClient(
g.Name(), "change",
@@ -1994,7 +1994,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
}
}(g.GetClients(nil))
default:
return group.UserError("unknown user action")
return hall.UserError("unknown user action")
}
case "pong":
// nothing
@@ -2004,7 +2004,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
})
default:
log.Printf("unexpected message: %v", m.Type)
return group.ProtocolError("unexpected message")
return hall.ProtocolError("unexpected message")
}
return nil
}
@@ -2199,7 +2199,7 @@ func (c *webClient) write(m clientMessage) error {
}
}
func broadcast(cs []group.Client, m clientMessage) error {
func broadcast(cs []hall.Client, m clientMessage) error {
b, err := json.Marshal(m)
if err != nil {
return err
@@ -2228,7 +2228,7 @@ func (c *webClient) close(data []byte) error {
func errorMessage(id string, err error) *clientMessage {
switch e := err.(type) {
case group.UserError:
case hall.UserError:
return &clientMessage{
Type: "usermessage",
Kind: "error",
@@ -2236,7 +2236,7 @@ func errorMessage(id string, err error) *clientMessage {
Privileged: true,
Value: e.Error(),
}
case group.KickError:
case hall.KickError:
message := e.Message
if message == "" {
message = "you have been kicked out"
+12 -12
View File
@@ -7,7 +7,7 @@ import (
"sync"
"git.stormux.org/storm/skald/conn"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/sdpfrag"
"github.com/pion/sdp/v3"
@@ -15,7 +15,7 @@ import (
)
type WhipClient struct {
group *group.Group
hall *hall.Hall
addr net.Addr
id string
token string
@@ -27,12 +27,12 @@ type WhipClient struct {
etag string
}
func NewWhipClient(g *group.Group, id string, token string, addr net.Addr) *WhipClient {
return &WhipClient{group: g, id: id, token: token, addr: addr}
func NewWhipClient(g *hall.Hall, id string, token string, addr net.Addr) *WhipClient {
return &WhipClient{hall: g, id: id, token: token, addr: addr}
}
func (c *WhipClient) Group() *group.Group {
return c.group
func (c *WhipClient) Hall() *hall.Hall {
return c.hall
}
func (c *WhipClient) Addr() net.Addr {
@@ -83,12 +83,12 @@ func (c *WhipClient) SetETag(etag string) {
c.etag = etag
}
func (c *WhipClient) PushConn(g *group.Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error {
func (c *WhipClient) PushConn(g *hall.Hall, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error {
return nil
}
func (c *WhipClient) RequestConns(target group.Client, g *group.Group, id string) error {
if g != c.group {
func (c *WhipClient) RequestConns(target hall.Client, g *hall.Hall, id string) error {
if g != c.hall {
return nil
}
@@ -122,7 +122,7 @@ func (c *WhipClient) Kick(id string, user *string, message string) error {
func (c *WhipClient) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
g := c.group
g := c.hall
if g == nil {
return nil
}
@@ -136,8 +136,8 @@ func (c *WhipClient) Close() error {
}
c.connection = nil
}
group.DelClient(c)
c.group = nil
hall.DelClient(c)
c.hall = nil
return nil
}
+3 -3
View File
@@ -1,6 +1,6 @@
# Skald's administrative API
Skald provides an HTTP-based API that can be used to create groups and
Skald provides an HTTP-based API that can be used to create halls and
users. For example, in order to create a hall, a client may do
PUT /skald-api/v0/.halls/hallname/
@@ -39,11 +39,11 @@ Provides a number of statistics about the running server, in JSON. The
exact format is undocumented, and may change between versions. The only
allowed methods are HEAD and GET.
### List of groups
### List of halls
/skald-api/v0/.halls/
Returns a list of groups, as a JSON array. The only allowed methods are
Returns a list of halls, as a JSON array. The only allowed methods are
HEAD and GET.
### Group definition
+4 -4
View File
@@ -56,17 +56,17 @@ The following instructions assume that your server is called
`skald.example.org` and that you have already created a dedicated user
called `skald`.
First, make sure that the `groups` and `data` directories exist:
First, make sure that the `halls` and `data` directories exist:
```sh
mkdir groups data
mkdir halls data
```
Now copy the `skald` binary, and the directories `static`, `data` and
`groups` to the server:
`halls` to the server:
```sh
rsync -a skald static data groups skald@skald.example.org:
rsync -a skald static data halls skald@skald.example.org:
```
If you don't have a TLS certificate, Skald will generate a self-signed
+14 -14
View File
@@ -15,7 +15,7 @@ import (
"time"
"git.stormux.org/storm/skald/diskwriter"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/ice"
"git.stormux.org/storm/skald/limit"
"git.stormux.org/storm/skald/token"
@@ -32,10 +32,10 @@ func main() {
"web server root `directory`")
flag.BoolVar(&webserver.Insecure, "insecure", false,
"act as an HTTP server rather than HTTPS")
flag.StringVar(&group.DataDirectory, "data", "./data/",
flag.StringVar(&hall.DataDirectory, "data", "./data/",
"data `directory`")
flag.StringVar(&group.Directory, "groups", "./groups/",
"group description `directory`")
flag.StringVar(&hall.Directory, "halls", "./halls/",
"hall description `directory`")
flag.StringVar(&diskwriter.Directory, "recordings", "./recordings/",
"recordings `directory`")
flag.StringVar(&cpuprofile, "cpuprofile", "",
@@ -46,7 +46,7 @@ func main() {
"store mutex profile in `file`")
flag.StringVar(&udpRange, "udp-range", "",
"UDP `port` (multiplexing) or port1-port2 (range)")
flag.BoolVar(&group.UseMDNS, "mdns", false, "gather mDNS addresses")
flag.BoolVar(&hall.UseMDNS, "mdns", false, "gather mDNS addresses")
flag.BoolVar(&ice.ICERelayOnly, "relay-only", false,
"require use of TURN relays for all media traffic")
flag.StringVar(&turnserver.Address, "turn", "auto",
@@ -63,14 +63,14 @@ func main() {
if n != 2 || min <= 0 || max <= 0 || min > max {
log.Fatalf("UDP range: bad range")
}
group.UDPMin = min
group.UDPMax = max
hall.UDPMin = min
hall.UDPMax = max
} else {
port, err := strconv.Atoi(udpRange)
if err != nil {
log.Fatalf("UDP: %v", err)
}
err = group.SetUDPMux(port)
err = hall.SetUDPMux(port)
if err != nil {
log.Fatalf("UDP: %v", err)
}
@@ -122,22 +122,22 @@ func main() {
log.Printf("File descriptor limit is %v, please increase it!", n)
}
ice.ICEFilename = filepath.Join(group.DataDirectory, "ice-servers.json")
ice.ICEFilename = filepath.Join(hall.DataDirectory, "ice-servers.json")
token.SetStatefulFilename(
filepath.Join(
filepath.Join(group.DataDirectory, "var"),
filepath.Join(hall.DataDirectory, "var"),
"tokens.jsonl",
),
)
// make sure the list of public groups is updated early
go group.Update()
// make sure the list of public halls is updated early
go hall.Update()
// causes the built-in server to start if required
ice.Update()
defer turnserver.Stop()
err = webserver.Serve(httpAddr, group.DataDirectory)
err = webserver.Serve(httpAddr, hall.DataDirectory)
if err != nil {
log.Fatalf("Server: %v", err)
}
@@ -157,7 +157,7 @@ func main() {
select {
case <-ticker.C:
go func() {
group.Update()
hall.Update()
token.Expire()
}()
case <-slowTicker.C:
+29 -29
View File
@@ -177,23 +177,23 @@ fields are as follows:
will be redirected to the canonical one.
## Group definitions
## Hall definitions
Groups are described by JSON files in the `groups/` directory. These
Halls are described by JSON files in the `halls/` directory. These
files are normally administered using the `skaldctl` utility, but may
also be edited manually (there is no need to restart the server).
### Managing groups using `skaldctl`
### Managing halls using `skaldctl`
#### Creating, modifying, and deleting groups
#### Creating, modifying, and deleting halls
A group is created using `skaldctl create-hall`:
A hall is created using `skaldctl create-hall`:
```sh
skaldctl create-hall -hall city-watch
```
There are a number of options to customise the behaviour of the group, see
There are a number of options to customise the behaviour of the hall, see
`skaldctl create-hall -help` for a full list. For example, in order to
create a hall that allows unrestricted creation of tokens, say:
@@ -203,30 +203,30 @@ skaldctl create-hall -hall city-watch -unrestricted-tokens
For more advanced configuration, `skaldctl create-hall` can be invoked
with the `-json` flag, in which case it takes a JSON template from standard
input. The syntax of a JSON template is just like that of a group
input. The syntax of a JSON template is just like that of a hall
definition file (see below), except that it must not contain the fields
`users` and `wildcard-user`. For example, in order to create a redirect
(see the section *Group description reference* below):
(see the section *Hall description reference* below):
```sh
echo '{"redirect": "https://skald.example.org:8443/hall/city-watch/"}' | skaldctl create-hall -hall amcw -json
```
Groups are modified using `skaldctl update-hall`:
Halls are modified using `skaldctl update-hall`:
```sh
skaldctl update-hall -hall city-watch -unrestricted-tokens=false
```
If a JSON template is provided to `skaldctl update-hall`, then it is
merged with the existing group configuration. Entries may be deleted
merged with the existing hall configuration. Entries may be deleted
by setting them to `null` in the template:
```sh
echo '{"redirect": null}' | skaldctl update-hall -hall amcw
```
A group is deleted using `skaldctl delete-hall`:
A hall is deleted using `skaldctl delete-hall`:
```sh
skaldctl delete-hall -hall amcw
@@ -266,7 +266,7 @@ skaldctl create-user -hall city-watch -wildcard
skaldctl set-password -hall city-watch -wildcard
```
For open groups, where any user can login with any password, the wildcard
For open halls, where any user can login with any password, the wildcard
user's password is set to the password of type `wildcard`:
```sh
@@ -278,17 +278,17 @@ password types.
#### Automatic subgroups
It is sometimes necessary to create a large number of identical groups.
It is sometimes necessary to create a large number of identical halls.
For example, the author has been using Skald to supervise computer
science practicals, where up to 40 students are working in groups of two.
science practicals, where up to 40 students are working in pairs.
While it is possible to automate the creation of groups by accessing
While it is possible to automate the creation of halls by accessing
Skald's API, by scripting calls to skaldctl, or by generating files
directly under `groups/`, Skald provides a facility called *automatic
subgroups* that can be used to generate groups on demand.
directly under `halls/`, Skald provides a facility called *automatic
subgroups* that can be used to generate halls on demand.
Automatic subgroups are enabled by setting the `"auto-subgroups"`
field in the group description:
field in the hall description:
```sh
skaldctl create-hall unseen-university -auto-subgroups
@@ -297,7 +297,7 @@ skaldctl create-hall unseen-university -auto-subgroups
Whenever a user attempts to access a subgroup of `unseen-university`, for
example `unseen-university/hex`, the group is created in memory and
persists until it is empty and its chat history has expired. The main
group's operator can view the list of populated subgroups with the command
hall's operator can view the list of populated subgroups with the command
`/subgroups`.
#### Managing tokens
@@ -313,26 +313,26 @@ skaldctl list-tokens -l -hall city-watch
```
A token that is generated with the `-include-subgroups` flag applies to
the whole hierarchy rooted at the given group, including both ordinary
groups and automatically generated subgroups.
the whole hierarchy rooted at the given hall, including both ordinary
halls and automatically generated subgroups.
```sh
skaldctl create-token -hall city-watch -include-subgroups
```
Such a token can be attached to the root of the group hierarchy, and
therefore be valid for any group on the server:
Such a token can be attached to the root of the hall hierarchy, and
therefore be valid for any hall on the server:
```sh
skaldctl create-token -hall '' -include-subgroups
```
### Group description reference
### Hall description reference
The definition for the group called *hallname* is in the file
`groups/hallname.json`; it does not contain the group name, which makes
The definition for the hall called *hallname* is in the file
`halls/hallname.json`; it does not contain the hall name, which makes
it easy to copy or link hall definitions. You may use subdirectories:
a file `groups/teaching/networking.json` defines a group called
a file `halls/teaching/networking.json` defines a hall called
*teaching/networking*.
Every hall definition file contains a single JSON dictionary. All fields
@@ -479,7 +479,7 @@ allows any username with any password.
### Hashed passwords
For security reasons, passwords are usually hashed before being stored in
group descriptions (in fact, the `skaldctl` utility does not even support
hall descriptions (in fact, the `skaldctl` utility does not even support
storing plaintext passwords). A hashed password is represented as a JSON
dictionary with a field `type` and a number of type-specific fields.
@@ -501,7 +501,7 @@ A user entry with a hashed password looks like this:
```
Hashed passwords are normally generated transparently to the user by the
`skaldctl set-password` command. When editing group description files
`skaldctl set-password` command. When editing hall description files
manually, hashed passwords can be generated with the `skaldctl hash-password`
utility.
+15 -15
View File
@@ -26,7 +26,7 @@ import (
"golang.org/x/crypto/pbkdf2"
"golang.org/x/term"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/token"
)
@@ -229,11 +229,11 @@ func readConfig(filename string) (configuration, error) {
return config, nil
}
func makePassword(pw string, algorithm string, iterations, length, saltlen, cost int) (group.Password, error) {
func makePassword(pw string, algorithm string, iterations, length, saltlen, cost int) (hall.Password, error) {
salt := make([]byte, saltlen)
_, err := rand.Read(salt)
if err != nil {
return group.Password{}, err
return hall.Password{}, err
}
switch algorithm {
@@ -242,7 +242,7 @@ func makePassword(pw string, algorithm string, iterations, length, saltlen, cost
[]byte(pw), salt, iterations, length, sha256.New,
)
encoded := hex.EncodeToString(key)
return group.Password{
return hall.Password{
Type: "pbkdf2",
Hash: "sha-256",
Key: &encoded,
@@ -254,11 +254,11 @@ func makePassword(pw string, algorithm string, iterations, length, saltlen, cost
[]byte(pw), cost,
)
if err != nil {
return group.Password{}, err
return hall.Password{}, err
}
k := string(key)
return group.Password{
return hall.Password{
Type: "bcrypt",
Key: &k,
}, nil
@@ -269,11 +269,11 @@ func makePassword(pw string, algorithm string, iterations, length, saltlen, cost
"must be the empty string",
)
}
return group.Password{
return hall.Password{
Type: "wildcard",
}, nil
default:
return group.Password{}, errors.New("unknown password type")
return hall.Password{}, errors.New("unknown password type")
}
}
@@ -392,18 +392,18 @@ func initialSetupCmd(cmdname string, args []string) {
defer skaldConfig.Close()
defer skaldctlConfig.Close()
var users map[string]group.UserDescription
var users map[string]hall.UserDescription
if adminPassword != "" {
pw, err := makePassword(adminPassword, "bcrypt", 0, 0, 0, 12)
if err != nil {
log.Fatalf("makePassword: %v", err)
}
perms, err := group.NewPermissions("admin")
perms, err := hall.NewPermissions("admin")
if err != nil {
log.Fatalf("NewPermissions: %v", err)
}
users = map[string]group.UserDescription{
users = map[string]hall.UserDescription{
adminUsername: {
Password: pw,
Permissions: perms,
@@ -411,7 +411,7 @@ func initialSetupCmd(cmdname string, args []string) {
}
}
config := group.Configuration{
config := hall.Configuration{
WritableGroups: true,
Users: users,
}
@@ -963,7 +963,7 @@ func parsePermissions(p string, expand bool) (any, error) {
if !expand {
return p, nil
}
pp, err := group.NewPermissions(p)
pp, err := hall.NewPermissions(p)
if err != nil {
return nil, err
}
@@ -985,7 +985,7 @@ func formatRawPermissions(permissions []string) string {
return fmt.Sprintf("[%s]", perms)
}
func formatPermissions(permissions group.Permissions) string {
func formatPermissions(permissions hall.Permissions) string {
s := permissions.String()
if len(s) > 0 && s[0] != '[' {
return s
@@ -1056,7 +1056,7 @@ func listUsersCmd(cmdname string, args []string) {
fmt.Printf("%-12s (ERROR=%v)\n", user, err)
continue
}
var d group.UserDescription
var d hall.UserDescription
_, err = getJSON(uu, &d)
if err != nil {
fmt.Printf("%-12s (ERROR=%v)\n", user, err)
+3 -3
View File
@@ -4,11 +4,11 @@ import (
"encoding/json"
"testing"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
)
func TestMakePassword(t *testing.T) {
doit := func(pw group.Password) {
doit := func(pw hall.Password) {
ok, _ := pw.Match("secret")
if !ok {
t.Errorf("%v didn't match", pw)
@@ -50,7 +50,7 @@ func TestFormatPermissions(t *testing.T) {
{`[]`, "[]", "[]"},
}
for _, test := range tests {
var p group.Permissions
var p hall.Permissions
err := json.Unmarshal([]byte(test.j), &p)
if err != nil {
t.Errorf("Unmarshal %#v: %v", test.j, err)
+1 -1
View File
@@ -4712,7 +4712,7 @@ async function start() {
if(groupStatus.name) {
group = groupStatus.name;
} else {
console.warn("no group name in status");
console.warn("no hall name in status");
group = decodeURIComponent(
location.pathname.replace(/^\/[a-z]*\//, '').replace(/\/$/, ''),
);
+3 -3
View File
@@ -5,7 +5,7 @@ import (
"sort"
"time"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
)
type GroupStats struct {
@@ -59,11 +59,11 @@ type Track struct {
}
func GetGroups() []GroupStats {
names := group.GetNames()
names := hall.GetNames()
gs := make([]GroupStats, 0, len(names))
for _, name := range names {
g := group.Get(name)
g := hall.Get(name)
if g == nil {
continue
}
+7 -7
View File
@@ -62,13 +62,13 @@ X Search for remaining Galene names and classify each survivor as attribution/hi
# PHASE 2: HALL TERMINOLOGY AND DATA MODEL
# ============================================================================
- Rename Go package group/ to hall/ if the package-wide rename stays tractable
- Rename package declarations, types, variables, funcs, tests, and comments from group to hall
- Rename Group type to Hall and update all compile errors intentionally
- Rename group description files/concepts to hall description files/concepts
- Rename default directory flag: -groups -> -halls
- Rename default data directory: groups/ -> halls/
- Rename public listing concept from public groups to public halls
X Rename Go package group/ to hall/ if the package-wide rename stays tractable
X Rename package declarations, types, variables, funcs, tests, and comments from group to hall
X Rename Group type to Hall and update all compile errors intentionally
X Rename group description files/concepts to hall description files/concepts
X Rename default directory flag: -groups -> -halls
X Rename default data directory: groups/ -> halls/
X Rename public listing concept from public groups to public halls
- Rename subgroups to subhalls everywhere, including JSON fields and commands
- Rename auto-subgroups -> auto-subhalls
- Rename allow-subgroups -> allow-subhalls
+24 -24
View File
@@ -13,7 +13,7 @@ import (
"golang.org/x/crypto/bcrypt"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/stats"
"git.stormux.org/storm/skald/token"
)
@@ -47,7 +47,7 @@ func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, hallname, user s
}
}
if ok && !wildcard && username == user {
desc, err := group.GetDescription(hallname)
desc, err := hall.GetDescription(hallname)
if err != nil {
internalError(w,
"Get description for group %v: %v",
@@ -193,7 +193,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
methodNotAllowed(w, "HEAD, GET")
return
}
groups, err := group.GetDescriptionNames()
groups, err := hall.GetDescriptionNames()
if err != nil {
httpError(w, err)
return
@@ -233,7 +233,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
}
if r.Method == "HEAD" || r.Method == "GET" {
desc, etag, err := group.GetSanitisedDescription(g)
desc, etag, err := hall.GetSanitisedDescription(g)
if err != nil {
httpError(w, err)
return
@@ -249,7 +249,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
sendJSON(w, r, desc)
return
} else if r.Method == "PUT" {
etag, err := group.GetDescriptionTag(g)
etag, err := hall.GetDescriptionTag(g)
if errors.Is(err, os.ErrNotExist) {
err = nil
etag = ""
@@ -263,12 +263,12 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
return
}
var newdesc group.Description
var newdesc hall.Description
done = getJSON(w, r, &newdesc)
if done {
return
}
err = group.UpdateDescription(g, etag, &newdesc)
err = hall.UpdateDescription(g, etag, &newdesc)
if err != nil {
httpError(w, err)
return
@@ -280,7 +280,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
}
return
} else if r.Method == "DELETE" {
etag, err := group.GetDescriptionTag(g)
etag, err := hall.GetDescriptionTag(g)
if err != nil {
httpError(w, err)
return
@@ -290,7 +290,7 @@ func apiGroupHandler(w http.ResponseWriter, r *http.Request, pth string) {
if done {
return
}
err = group.DeleteDescription(g, etag)
err = hall.DeleteDescription(g, etag)
if err != nil {
httpError(w, err)
return
@@ -318,7 +318,7 @@ func usersHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
methodNotAllowed(w, "HEAD, GET")
return
}
users, etag, err := group.GetUsers(g)
users, etag, err := hall.GetUsers(g)
if err != nil {
httpError(w, err)
return
@@ -371,7 +371,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar
}
if r.Method == "HEAD" || r.Method == "GET" {
user, etag, err := group.GetSanitisedUser(g, user, wildcard)
user, etag, err := hall.GetSanitisedUser(g, user, wildcard)
if err != nil {
httpError(w, err)
return
@@ -384,7 +384,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar
sendJSON(w, r, user)
return
} else if r.Method == "PUT" {
etag, err := group.GetUserTag(g, user, wildcard)
etag, err := hall.GetUserTag(g, user, wildcard)
if errors.Is(err, os.ErrNotExist) {
etag = ""
err = nil
@@ -398,12 +398,12 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar
return
}
var newdesc group.UserDescription
var newdesc hall.UserDescription
done = getJSON(w, r, &newdesc)
if done {
return
}
err = group.UpdateUser(g, user, wildcard, etag, &newdesc)
err = hall.UpdateUser(g, user, wildcard, etag, &newdesc)
if err != nil {
httpError(w, err)
return
@@ -415,7 +415,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar
}
return
} else if r.Method == "DELETE" {
etag, err := group.GetUserTag(g, user, wildcard)
etag, err := hall.GetUserTag(g, user, wildcard)
if err != nil {
httpError(w, err)
return
@@ -426,7 +426,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string, wildcar
return
}
err = group.DeleteUser(g, user, wildcard, etag)
err = hall.DeleteUser(g, user, wildcard, etag)
if err != nil {
httpError(w, err)
return
@@ -447,12 +447,12 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string, wil
}
if r.Method == "PUT" {
var pw group.Password
var pw hall.Password
done := getJSON(w, r, &pw)
if done {
return
}
err := group.SetUserPassword(g, user, wildcard, pw)
err := hall.SetUserPassword(g, user, wildcard, pw)
if err != nil {
httpError(w, err)
return
@@ -471,11 +471,11 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string, wil
}
k := string(key)
pw := group.Password{
pw := hall.Password{
Type: "bcrypt",
Key: &k,
}
err = group.SetUserPassword(g, user, wildcard, pw)
err = hall.SetUserPassword(g, user, wildcard, pw)
if err != nil {
httpError(w, err)
return
@@ -483,7 +483,7 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string, wil
w.WriteHeader(http.StatusNoContent)
return
} else if r.Method == "DELETE" {
err := group.SetUserPassword(g, user, wildcard, group.Password{})
err := hall.SetUserPassword(g, user, wildcard, hall.Password{})
if err != nil {
httpError(w, err)
return
@@ -528,7 +528,7 @@ func keysHandler(w http.ResponseWriter, r *http.Request, g string) {
httpError(w, err)
return
}
err = group.SetKeys(g, keys.Keys)
err = hall.SetKeys(g, keys.Keys)
if err != nil {
httpError(w, err)
return
@@ -536,7 +536,7 @@ func keysHandler(w http.ResponseWriter, r *http.Request, g string) {
w.WriteHeader(http.StatusNoContent)
return
} else if r.Method == "DELETE" {
err := group.SetKeys(g, nil)
err := hall.SetKeys(g, nil)
if err != nil {
httpError(w, err)
return
@@ -563,7 +563,7 @@ func tokensHandler(w http.ResponseWriter, r *http.Request, g, pth string) {
if g != "" {
// check that the group exists
_, err := group.GetDescription(g)
_, err := hall.GetDescription(g)
if err != nil {
httpError(w, err)
return
+12 -12
View File
@@ -15,7 +15,7 @@ import (
"path/filepath"
"testing"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/token"
)
@@ -34,8 +34,8 @@ func setup() {
func setupTest(dir, datadir string) error {
setup()
group.Directory = dir
group.DataDirectory = datadir
hall.Directory = dir
hall.DataDirectory = datadir
config := `{
"writableGroups": true,
"users": {
@@ -45,7 +45,7 @@ func setupTest(dir, datadir string) error {
}
}
}`
f, err := os.Create(filepath.Join(group.DataDirectory, "config.json"))
f, err := os.Create(filepath.Join(hall.DataDirectory, "config.json"))
if err != nil {
return err
}
@@ -142,7 +142,7 @@ func TestApi(t *testing.T) {
t.Errorf("Create hall: %v %v", err, resp.StatusCode)
}
var desc *group.Description
var desc *hall.Description
err = getJSON("/skald-api/v0/.halls/test/", &desc)
if err != nil || len(desc.Users) != 0 {
t.Errorf("Get hall: %v", err)
@@ -223,7 +223,7 @@ func TestApi(t *testing.T) {
t.Errorf("Set password (POST): %v %v", err, resp.StatusCode)
}
var user group.UserDescription
var user hall.UserDescription
err = getJSON("/skald-api/v0/.halls/test/.users/jch", &user)
if err != nil {
t.Errorf("Get user: %v", err)
@@ -232,7 +232,7 @@ func TestApi(t *testing.T) {
t.Errorf("User not sanitised properly")
}
desc, err = group.GetDescription("test")
desc, err = hall.GetDescription("test")
if err != nil {
t.Errorf("GetDescription: %v", err)
}
@@ -251,7 +251,7 @@ func TestApi(t *testing.T) {
t.Errorf("Delete hall: %v %v", err, resp.StatusCode)
}
desc, err = group.GetDescription("test")
desc, err = hall.GetDescription("test")
if err != nil {
t.Errorf("GetDescription: %v", err)
}
@@ -272,7 +272,7 @@ func TestApi(t *testing.T) {
t.Errorf("Get wildcard user: %v", err)
}
desc, err = group.GetDescription("test")
desc, err = hall.GetDescription("test")
if err != nil {
t.Errorf("GetDescription: %v", err)
}
@@ -287,7 +287,7 @@ func TestApi(t *testing.T) {
t.Errorf("Delete wildcard user: %v %v", err, resp.StatusCode)
}
desc, err = group.GetDescription("test")
desc, err = hall.GetDescription("test")
if err != nil {
t.Errorf("GetDescription: %v", err)
}
@@ -414,7 +414,7 @@ func TestApi(t *testing.T) {
t.Errorf("Delete hall: %v %v", err, resp.StatusCode)
}
_, err = group.GetDescription("test")
_, err = hall.GetDescription("test")
if !errors.Is(err, os.ErrNotExist) {
t.Errorf("Group exists after delete")
}
@@ -451,7 +451,7 @@ func TestApiBadAuth(t *testing.T) {
do("GET", "/skald-api/v0/.halls/")
do("PUT", "/skald-api/v0/.halls/test/")
f, err := os.Create(filepath.Join(group.Directory, "test.json"))
f, err := os.Create(filepath.Join(hall.Directory, "test.json"))
if err != nil {
t.Fatalf("Create(test.json): %v", err)
}
+22 -22
View File
@@ -22,7 +22,7 @@ import (
"github.com/gorilla/websocket"
"git.stormux.org/storm/skald/diskwriter"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/rtpconn"
"github.com/jech/cert"
)
@@ -64,7 +64,7 @@ func Serve(address string, dataDir string) error {
}
}
s.RegisterOnShutdown(func() {
group.Shutdown("server is shutting down")
hall.Shutdown("server is shutting down")
})
server = s
@@ -130,11 +130,11 @@ func httpError(w http.ResponseWriter, err error) {
notFound(w)
return
}
if errors.Is(err, group.ErrUnknownPermission) {
if errors.Is(err, hall.ErrUnknownPermission) {
http.Error(w, "unknown permission", http.StatusBadRequest)
return
}
var autherr *group.NotAuthorisedError
var autherr *hall.NotAuthorisedError
if errors.As(err, &autherr) {
log.Printf("HTTP server error: %v", err)
http.Error(w, "not authorised", http.StatusUnauthorized)
@@ -160,7 +160,7 @@ const (
)
func redirect(w http.ResponseWriter, r *http.Request) bool {
conf, err := group.GetConfiguration()
conf, err := hall.GetConfiguration()
if err != nil || conf.CanonicalHost == "" {
return false
}
@@ -285,7 +285,7 @@ func serveFile(w http.ResponseWriter, r *http.Request, p string) {
http.ServeContent(w, r, fi.Name(), fi.ModTime(), f)
}
func parseGroupName(prefix string, p string) string {
func parseHallName(prefix string, p string) string {
if !strings.HasPrefix(p, prefix) {
return ""
}
@@ -346,13 +346,13 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
return
}
name := parseGroupName("/hall/", r.URL.Path)
name := parseHallName("/hall/", r.URL.Path)
if name == "" {
notFound(w)
return
}
g, err := group.Add(name, nil)
g, err := hall.Add(name, nil)
if err != nil {
httpError(w, err)
return
@@ -375,7 +375,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
}
func baseURL(r *http.Request) (*url.URL, error) {
conf, err := group.GetConfiguration()
conf, err := hall.GetConfiguration()
if err != nil {
return nil, err
}
@@ -415,13 +415,13 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) {
internalError(w, "groupStatusHandler: this shouldn't happen")
return
}
name := parseGroupName("/hall/", pth)
name := parseHallName("/hall/", pth)
if name == "" {
notFound(w)
return
}
g, err := group.Add(name, nil)
g, err := hall.Add(name, nil)
if err != nil {
httpError(w, err)
return
@@ -458,13 +458,13 @@ func publicHandler(w http.ResponseWriter, r *http.Request) {
return
}
g := group.GetPublic(base)
g := hall.GetPublic(base)
e := json.NewEncoder(w)
e.Encode(g)
}
func adminMatch(username, password string) (bool, error) {
conf, err := group.GetConfiguration()
conf, err := hall.GetConfiguration()
if err != nil {
return false, err
}
@@ -512,7 +512,7 @@ func CheckOrigin(w http.ResponseWriter, r *http.Request, admin bool) bool {
if err == nil && strings.EqualFold(o.Host, r.Host) {
ok = true
} else {
conf, err := group.GetConfiguration()
conf, err := hall.GetConfiguration()
if err != nil {
return false
}
@@ -613,20 +613,20 @@ func recordingsHandler(w http.ResponseWriter, r *http.Request) {
for len(p) > 0 && p[len(p)-1] == '/' {
p = p[:len(p)-1]
}
group = parseGroupName("/", p)
group = parseHallName("/", p)
if group == "" {
http.Error(w, "Bad group name", http.StatusBadRequest)
http.Error(w, "Bad hall name", http.StatusBadRequest)
return
}
} else {
if p[len(p)-1] == '/' {
http.Error(w, "Bad group name", http.StatusBadRequest)
http.Error(w, "Bad hall name", http.StatusBadRequest)
return
}
group, filename = path.Split(p)
group = parseGroupName("/", group)
group = parseHallName("/", group)
if group == "" {
http.Error(w, "Bad group name", http.StatusBadRequest)
http.Error(w, "Bad hall name", http.StatusBadRequest)
return
}
}
@@ -711,13 +711,13 @@ func checkGroupPermissions(w http.ResponseWriter, r *http.Request, hallname stri
return false
}
g := group.Get(hallname)
g := hall.Get(hallname)
if g == nil {
return false
}
_, p, err := g.GetPermission(
group.ClientCredentials{
hall.ClientCredentials{
Username: &user,
Password: pass,
},
@@ -732,7 +732,7 @@ func checkGroupPermissions(w http.ResponseWriter, r *http.Request, hallname stri
}
}
if err != nil || !record {
var autherr *group.NotAuthorisedError
var autherr *hall.NotAuthorisedError
if errors.As(err, &autherr) {
time.Sleep(200 * time.Millisecond)
}
+6 -6
View File
@@ -10,10 +10,10 @@ import (
"github.com/pion/webrtc/v4"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
)
func TestParseGroupName(t *testing.T) {
func TestParseHallName(t *testing.T) {
a := []struct{ p, g string }{
{"", ""},
{"/foo", ""},
@@ -29,7 +29,7 @@ func TestParseGroupName(t *testing.T) {
}
for _, pg := range a {
g := parseGroupName("/hall/", pg.p)
g := parseHallName("/hall/", pg.p)
if g != pg.g {
t.Errorf("Path %v, got %v, expected %v",
pg.p, g, pg.g)
@@ -58,10 +58,10 @@ func TestBase(t *testing.T) {
}
dir := t.TempDir()
group.DataDirectory = dir
hall.DataDirectory = dir
for _, v := range a {
conf := group.Configuration{
conf := hall.Configuration{
ProxyURL: v.p,
}
c, err := json.Marshal(conf)
@@ -200,7 +200,7 @@ func TestFormatICEServer(t *testing.T) {
func TestMatchAdmin(t *testing.T) {
d := t.TempDir()
group.DataDirectory = d
hall.DataDirectory = d
filename := filepath.Join(d, "config.json")
f, err := os.Create(filename)
+10 -10
View File
@@ -17,7 +17,7 @@ import (
"github.com/pion/webrtc/v4"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/hall"
"git.stormux.org/storm/skald/ice"
"git.stormux.org/storm/skald/rtpconn"
"git.stormux.org/storm/skald/sdpfrag"
@@ -153,13 +153,13 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) {
return
}
name := parseGroupName("/hall/", pth)
name := parseHallName("/hall/", pth)
if name == "" {
notFound(w)
return
}
g, err := group.Add(name, nil)
g, err := hall.Add(name, nil)
if err != nil {
httpError(w, err)
return
@@ -199,7 +199,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) {
token := parseBearerToken(r.Header.Get("Authorization"))
whip := "whip"
creds := group.ClientCredentials{
creds := hall.ClientCredentials{
Username: &whip,
Token: token,
}
@@ -221,7 +221,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) {
c := rtpconn.NewWhipClient(g, id, token, addr)
_, err = group.AddClient(g.Name(), c, creds)
_, err = hall.AddClient(g.Name(), c, creds)
if err != nil {
log.Printf("WHIP: %v", err)
httpError(w, err)
@@ -229,7 +229,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) {
}
if !canPresent(c.Permissions()) {
group.DelClient(c)
hall.DelClient(c)
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
@@ -238,7 +238,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) {
answer, err := c.NewConnection(r.Context(), body)
if err != nil {
group.DelClient(c)
hall.DelClient(c)
log.Printf("WHIP offer: %v", err)
httpError(w, err)
return
@@ -269,13 +269,13 @@ func whipResourceHandler(w http.ResponseWriter, r *http.Request) {
return
}
name := parseGroupName("/hall/", pth)
name := parseHallName("/hall/", pth)
if name == "" {
notFound(w)
return
}
g := group.Get(name)
g := hall.Get(name)
if g == nil {
notFound(w)
return
@@ -295,7 +295,7 @@ func whipResourceHandler(w http.ResponseWriter, r *http.Request) {
if t := c.Token(); t != "" {
token := parseBearerToken(r.Header.Get("Authorization"))
if !group.ConstantTimeCompare(t, token) {
if !hall.ConstantTimeCompare(t, token) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}