Rename public group URLs to halls

This commit is contained in:
Storm Dragon
2026-05-17 22:22:46 -04:00
parent bda0e548d3
commit 55528b8e62
21 changed files with 312 additions and 312 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/n
./skald &
```
Point your browser at <https://localhost:8443/group/night-watch/>, ignore
Point your browser at <https://localhost:8443/hall/night-watch/>, ignore
the unknown certificate warning, and log in with username *vimes* and
password *sybil*.
+21 -21
View File
@@ -1,22 +1,22 @@
# Skald's administrative API
Skald provides an HTTP-based API that can be used to create groups and
users. For example, in order to create a group, a client may do
users. For example, in order to create a hall, a client may do
PUT /skald-api/v0/.groups/groupname/
PUT /skald-api/v0/.halls/hallname/
Content-Type: application/json
If-None-Match: *
The `If-None-Match` header avoids overwriting an existing group.
In order to edit a group definition, a client first does
In order to edit a hall definition, a client first does
GET /skald-api/v0/.groups/groupname/
GET /skald-api/v0/.halls/hallname/
This yields the group definition and an entity tag (in the ETag header).
This yields the hall definition and an entity tag (in the ETag header).
The client then modifies the group defintion, and does
PUT /skald-api/v0/.groups/groupname/
PUT /skald-api/v0/.halls/hallname/
If-Match: "abcd"
where "abcd" is the entity tag returned by the GET request. If the group
@@ -41,23 +41,23 @@ allowed methods are HEAD and GET.
### List of groups
/skald-api/v0/.groups/
/skald-api/v0/.halls/
Returns a list of groups, as a JSON array. The only allowed methods are
HEAD and GET.
### Group definition
/skald-api/v0/.groups/groupname
/skald-api/v0/.halls/hallname
Contains a "sanitised" group definition in JSON format, analogous to the
Contains a "sanitised" hall definition in JSON format, analogous to the
on-disk format but without any user definitions or cryptographic keys.
Allowed methods are HEAD, GET, PUT and DELETE. The only accepted
content-type is `application/json`.
### Authentication keys
/skald-api/v0/.groups/groupname/.keys
/skald-api/v0/.halls/hallname/.keys
Contains the keys used for validation of stateless tokens, encoded as
a JSON key set (RFC 7517). Allowed methods are PUT and DELETE. The only
@@ -65,16 +65,16 @@ accepted content-type is `application/jwk-set+json`.
### List of users
/skald-api/v0/.groups/groupname/.users/
/skald-api/v0/.halls/hallname/.users/
Returns a list of users, as a JSON array. The only allowed methods are
HEAD and GET.
### User definitions
/skald-api/v0/.groups/groupname/.users/username
/skald-api/v0/.groups/groupname/.empty-user
/skald-api/v0/.groups/groupname/.wildcard-user
/skald-api/v0/.halls/hallname/.users/username
/skald-api/v0/.halls/hallname/.empty-user
/skald-api/v0/.halls/hallname/.wildcard-user
Contains a "sanitised" user definition (without any passwords), a JSON
object with a single field `permissions`. The entries `.empty-user` and
@@ -84,9 +84,9 @@ only accepted content-type is `application/json`.
### Passwords
/skald-api/v0/.groups/groupname/.users/username/.password
/skald-api/v0/.groups/groupname/.empty-user/.password
/skald-api/v0/.groups/groupname/.wildcard-user/.password
/skald-api/v0/.halls/hallname/.users/username/.password
/skald-api/v0/.halls/hallname/.empty-user/.password
/skald-api/v0/.halls/hallname/.wildcard-user/.password
Contains the password of a given user. The PUT method takes a full
password definition, identical to what can appear in the `"password"`
@@ -97,7 +97,7 @@ POST.
### Wildcard user
/skald-api/v0/.groups/groupname/.wildcard-user
/skald-api/v0/.halls/hallname/.wildcard-user
Contains a dictionary defining the wildcard user, in the same format as
the dictionary defining an ordinary user. Allowed methods are HEAD, GET,
@@ -105,14 +105,14 @@ PUT and DELETE.
### Wildcard user password
/skald-api/v0/.groups/groupname/.wildcard-user/.password
/skald-api/v0/.halls/hallname/.wildcard-user/.password
This is analogous to the password of an ordinary user. Allowed methods
are PUT, POST and DELETE.
### List of stateful tokens
/skald-api/v0/.groups/groupname/.users/username/.tokens/
/skald-api/v0/.halls/hallname/.users/username/.tokens/
GET returns the list of stateful tokens, as a JSON array. POST creates
a new token, and returns its name in the `Location` header. Allowed
@@ -120,7 +120,7 @@ methods are HEAD, GET and POST.
### Stateful token
/skald-api/v0/.groups/groupname/.users/username/.tokens/token
/skald-api/v0/.halls/hallname/.users/username/.tokens/token
The full contents of a single token, in JSON. The exact format may change
between versions, so a client should first GET a token, update one or more
+10 -10
View File
@@ -159,46 +159,46 @@ rsync config.json skald@skald.example.org:data/
Create a group:
```sh
skaldctl create-group -group city-watch
skaldctl create-hall -hall city-watch
```
If you didn't install a TLS certificate above, you will need to run
`skaldctl` with the flag `-insecure`:
```sh
skaldctl -insecure create-group -group city-watch
skaldctl -insecure create-hall -hall city-watch
```
Create an "op", a user with group moderation privileges:
```sh
skaldctl create-user -group city-watch -user vimes -permissions op
skaldctl create-user -hall city-watch -user vimes -permissions op
```
Set the new user's password:
```sh
skaldctl set-password -group city-watch -user vimes
skaldctl set-password -hall city-watch -user vimes
```
You should now be able to test your Skald installation by pointing a web
browser at <https://skald.example.org:8443/group/city-watch/>.
browser at <https://skald.example.org:8443/hall/city-watch/>.
Create an ordinary user:
```sh
skaldctl create-user -group city-watch -user fred
skaldctl set-password -group city-watch -user fred
skaldctl create-user -hall city-watch -user fred
skaldctl set-password -hall city-watch -user fred
```
Check the results:
```sh
skaldctl list-groups
skaldctl list-users -l -group city-watch
skaldctl list-halls
skaldctl list-users -l -hall city-watch
```
Type `skaldctl -help`, `skaldctl create-group -help`, etc. for more
Type `skaldctl -help`, `skaldctl create-hall -help`, etc. for more
information.
## Advanced configuration
+4 -4
View File
@@ -30,7 +30,7 @@ server to client direction.
The client needs to know the location of the group, the (user-visible) URL
at which the group is found. This may be obtained either by explicit
configuration by the user, or by parsing the `/public-groups.json` file
configuration by the user, or by parsing the `/public-halls.json` file
which may contain an array of group statuses (see below).
A client then performs an HTTP GET request on the file `.status` at
@@ -558,7 +558,7 @@ a POST request to the authorisation server URL containing in its body
a JSON dictionary of the following form:
```javascript
{
"location": "https://skald.example.org/group/groupname/",
"location": "https://skald.example.org/hall/hallname/",
"username": username,
"password": password
}
@@ -575,7 +575,7 @@ a signed JWT (a "JWS") the body of which has the following form:
```javascript
{
"sub": username,
"aud": "https://skald.example.org/group/groupname/",
"aud": "https://skald.example.org/hall/hallname/",
"permissions": ["present"],
"iat": now,
"exp": now + 30s,
@@ -599,4 +599,4 @@ authentication portal performs authorisation, generates a token as above,
then redirects back to the group's URL with the token stores in a URL
query parameter named `token`:
https://skald.example.org/group/groupname/?token=eyJhbG...
https://skald.example.org/hall/hallname/?token=eyJhbG...
+32 -32
View File
@@ -23,7 +23,7 @@ your browser directly at the desired group URL.
### The group pages
A group named e.g. *city-watch* has an associated page
`/group/city-watch/`. After login, it presents an interface consisting
`/hall/city-watch/`. After login, it presents an interface consisting
of three panes:
- the left side pane contains the list of users who have joined the
@@ -71,7 +71,7 @@ a *user menu*, a menu that applies to that specific user.
The centre pane is a traditional chat interface, with an input form at the
bottom and the chat history above it. Chat history is never saved to
disk, and is erased after four hours (or whatever is specified in the
`"max-history-age"` field of the group definition).
`"max-history-age"` field of the hall definition).
Double-clicking on a message opens a contextual menu.
@@ -87,7 +87,7 @@ commands.
In order to generate an invitation link, choose the entry *Invite user* in
the group menu. This generates a link of the form
https://skald.example.org:8443/group/city-watch/?token=XXX
https://skald.example.org:8443/hall/city-watch/?token=XXX
where the *XXX* part, known as the *token*, is a shared secret. Such
a link allows password-less login to the group, and may therefore be
@@ -160,7 +160,7 @@ fields are as follows:
only meaningful permission is `"admin"`;
- `writableGroups`: if true, then the API used by `skaldctl` can be used
to modify group definitions; if unset or false, then only read-only
to modify hall definitions; if unset or false, then only read-only
access is allowed;
- `allowOrigin` is an array containing the list of HTTP origins that
@@ -187,21 +187,21 @@ also be edited manually (there is no need to restart the server).
#### Creating, modifying, and deleting groups
A group is created using `skaldctl create-group`:
A group is created using `skaldctl create-hall`:
```sh
skaldctl create-group -group city-watch
skaldctl create-hall -hall city-watch
```
There are a number of options to customise the behaviour of the group, see
`skaldctl create-group -help` for a full list. For example, in order to
create a group that allows unrestricted creation of tokens, say:
`skaldctl create-hall -help` for a full list. For example, in order to
create a hall that allows unrestricted creation of tokens, say:
```sh
skaldctl create-group -group city-watch -unrestricted-tokens
skaldctl create-hall -hall city-watch -unrestricted-tokens
```
For more advanced configuration, `skaldctl create-group` can be invoked
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
definition file (see below), except that it must not contain the fields
@@ -209,27 +209,27 @@ definition file (see below), except that it must not contain the fields
(see the section *Group description reference* below):
```sh
echo '{"redirect": "https://skald.example.org:8443/group/city-watch/"}' | skaldctl create-group -group amcw -json
echo '{"redirect": "https://skald.example.org:8443/hall/city-watch/"}' | skaldctl create-hall -hall amcw -json
```
Groups are modified using `skaldctl update-group`:
Groups are modified using `skaldctl update-hall`:
```sh
skaldctl update-group -group city-watch -unrestricted-tokens=false
skaldctl update-hall -hall city-watch -unrestricted-tokens=false
```
If a JSON template is provided to `skaldctl update-group`, then it is
If a JSON template is provided to `skaldctl update-hall`, then it is
merged with the existing group configuration. Entries may be deleted
by setting them to `null` in the template:
```sh
echo '{"redirect": null}' | skaldctl update-group -group amcw
echo '{"redirect": null}' | skaldctl update-hall -hall amcw
```
A group is deleted using `skaldctl delete-group`:
A group is deleted using `skaldctl delete-hall`:
```sh
skaldctl delete-group -group amcw
skaldctl delete-hall -hall amcw
```
#### Creating, modifying, and deleting users
@@ -237,7 +237,7 @@ skaldctl delete-group -group amcw
A user entry is created with the `skaldctl create-user` command:
```sh
skaldctl create-user -group city-watch -user vimes -permissions op
skaldctl create-user -hall city-watch -user vimes -permissions op
```
If the `-permissions` flag is not specified, it defaults to `present`,
@@ -253,7 +253,7 @@ In order to be useful, a user entry needs to be assigned a password. This
is done with the `skaldctl set-password` command:
```sh
skaldctl set-password -group city-watch -user vimes
skaldctl set-password -hall city-watch -user vimes
```
#### The fallback user
@@ -262,15 +262,15 @@ It is sometimes useful to allow multiple users to log in using the same
password. This can be achieved by defining the *wildcard* user:
```sh
skaldctl create-user -group city-watch -wildcard
skaldctl set-password -group city-watch -wildcard
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
user's password is set to the password of type `wildcard`:
```sh
skaldctl set-password -group city-watch -wildcard -type wildcard
skaldctl set-password -hall city-watch -wildcard -type wildcard
```
See the section *Client authorisation* below for more information about
@@ -291,7 +291,7 @@ Automatic subgroups are enabled by setting the `"auto-subgroups"`
field in the group description:
```sh
skaldctl create-group unseen-university -auto-subgroups
skaldctl create-hall unseen-university -auto-subgroups
```
Whenever a user attempts to access a subgroup of `unseen-university`, for
@@ -308,8 +308,8 @@ the `skaldctl` utility's `create-token`, `revoke-token`, `delete-token`
and `list-tokens` commands:
```sh
skaldctl create-token -group city-watch
skaldctl list-tokens -l -group city-watch
skaldctl create-token -hall city-watch
skaldctl list-tokens -l -hall city-watch
```
A token that is generated with the `-include-subgroups` flag applies to
@@ -317,25 +317,25 @@ the whole hierarchy rooted at the given group, including both ordinary
groups and automatically generated subgroups.
```sh
skaldctl create-token -group city-watch -include-subgroups
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:
```sh
skaldctl create-token -group '' -include-subgroups
skaldctl create-token -hall '' -include-subgroups
```
### Group description reference
The definition for the group called *groupname* is in the file
`groups/groupname.json`; it does not contain the group name, which makes
it easy to copy or link group definitions. You may use subdirectories:
The definition for the group called *hallname* is in the file
`groups/hallname.json`; it does not contain the group 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
*teaching/networking*.
Every group definition file contains a single JSON dictionary. All fields
Every hall definition file contains a single JSON dictionary. All fields
are optional. The following fields are allowed:
- `users`: a dictionary that maps user names to user descriptions (see
@@ -352,7 +352,7 @@ are optional. The following fields are allowed:
displayed at the top of the group page;
- `description`: a human-readable description of the group; this is
displayed on the landing page for public groups;
displayed on the landing page for public halls;
- `contact`: a human-readable contact for this group, such as an e-mail
address, ignored by the server;
+87 -87
View File
@@ -65,25 +65,25 @@ var commands = map[string]command{
command: deletePasswordCmd,
description: "delete a user's password",
},
"list-groups": {
"list-halls": {
command: listGroupsCmd,
description: "list groups",
description: "list halls",
},
"show-group": {
"show-hall": {
command: showGroupCmd,
description: "show group definition",
description: "show hall definition",
},
"create-group": {
"create-hall": {
command: createGroupCmd,
description: "create a group",
description: "create a hall",
},
"update-group": {
"update-hall": {
command: updateGroupCmd,
description: "change a group's definition",
description: "change a hall's definition",
},
"delete-group": {
"delete-hall": {
command: deleteGroupCmd,
description: "delete a group",
description: "delete a hall",
},
"list-users": {
command: listUsersCmd,
@@ -593,7 +593,7 @@ func deleteValue(url string) error {
}
func setPasswordCmd(cmdname string, args []string) {
var groupname, username string
var hallname, username string
var wildcard bool
var password, algorithm string
var iterations, cost, length, saltlen int
@@ -603,7 +603,7 @@ func setPasswordCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "set wildcard user's password")
cmd.StringVar(&password, "password", "", "new `password`")
@@ -622,9 +622,9 @@ func setPasswordCmd(cmdname string, args []string) {
os.Exit(1)
}
if groupname == "" {
if hallname == "" {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
@@ -655,12 +655,12 @@ func setPasswordCmd(cmdname string, args []string) {
var u string
if wildcard {
u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
".wildcard-user/.password",
)
} else {
u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
".users", username, ".password",
)
}
@@ -675,7 +675,7 @@ func setPasswordCmd(cmdname string, args []string) {
}
func deletePasswordCmd(cmdname string, args []string) {
var groupname, username string
var hallname, username string
var wildcard bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
@@ -683,7 +683,7 @@ func deletePasswordCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "set wildcard user's password")
cmd.Parse(args)
@@ -693,9 +693,9 @@ func deletePasswordCmd(cmdname string, args []string) {
os.Exit(1)
}
if groupname == "" {
if hallname == "" {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
@@ -710,12 +710,12 @@ func deletePasswordCmd(cmdname string, args []string) {
var err error
if wildcard {
u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
".wildcard-user/.password",
)
} else {
u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
".users", username, ".password",
)
}
@@ -796,7 +796,7 @@ func stdinJSON(doit bool) (map[string]any, error) {
}
func createGroupCmd(cmdname string, args []string) {
var groupname string
var hallname string
var unrestrictedTokens, autoSubgroups boolOption
var doJSON bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
@@ -804,7 +804,7 @@ func createGroupCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.Var(&unrestrictedTokens, "unrestricted-tokens",
"allow ordinary users to create tokens",
)
@@ -821,14 +821,14 @@ func createGroupCmd(cmdname string, args []string) {
os.Exit(1)
}
if groupname == "" {
if hallname == "" {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
)
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -848,18 +848,18 @@ func createGroupCmd(cmdname string, args []string) {
err = putJSON(u, data, false)
if err != nil {
log.Fatalf("Create group: %v", err)
log.Fatalf("Create hall: %v", err)
}
}
func deleteGroupCmd(cmdname string, args []string) {
var groupname string
var hallname string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname,
"%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.Parse(args)
if cmd.NArg() != 0 {
@@ -867,14 +867,14 @@ func deleteGroupCmd(cmdname string, args []string) {
os.Exit(1)
}
if groupname == "" {
if hallname == "" {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
)
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -882,12 +882,12 @@ func deleteGroupCmd(cmdname string, args []string) {
err = deleteValue(u)
if err != nil {
log.Fatalf("Delete group: %v", err)
log.Fatalf("Delete hall: %v", err)
}
}
func updateGroupCmd(cmdname string, args []string) {
var groupname string
var hallname string
var unrestrictedTokens, autoSubgroups boolOption
var doJSON bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
@@ -895,7 +895,7 @@ func updateGroupCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.Var(&unrestrictedTokens, "unrestricted-tokens",
"allow ordinary users to create tokens",
)
@@ -913,7 +913,7 @@ func updateGroupCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
)
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -1007,25 +1007,25 @@ func match(patterns []string, value string) (bool, error) {
}
func listUsersCmd(cmdname string, args []string) {
var groupname string
var hallname string
var long bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname,
"%v [option...] %v [option...] [pattern...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.BoolVar(&long, "l", false, "display permissions")
cmd.Parse(args)
patterns := cmd.Args()
if groupname == "" {
if hallname == "" {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname,
u, err := url.JoinPath(serverURL, "/skald-api/v0/.halls/", hallname,
".users/")
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -1069,21 +1069,21 @@ func listUsersCmd(cmdname string, args []string) {
}
}
func userURL(wildcard bool, groupname, username string) (string, error) {
func userURL(wildcard bool, hallname, username string) (string, error) {
if wildcard {
return url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
".wildcard-user",
)
}
return url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
".users", username,
)
}
func createUserCmd(cmdname string, args []string) {
var groupname, username string
var hallname, username string
var wildcard bool
var permissions stringOption
var doJSON bool
@@ -1092,7 +1092,7 @@ func createUserCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "create the wildcard user")
cmd.Var(&permissions, "permissions",
@@ -1107,9 +1107,9 @@ func createUserCmd(cmdname string, args []string) {
os.Exit(1)
}
if groupname == "" {
if hallname == "" {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
@@ -1131,7 +1131,7 @@ func createUserCmd(cmdname string, args []string) {
}
}
u, err := userURL(wildcard, groupname, username)
u, err := userURL(wildcard, hallname, username)
if err != nil {
log.Fatalf("Build URL: %v", err)
}
@@ -1155,7 +1155,7 @@ func createUserCmd(cmdname string, args []string) {
}
func updateUserCmd(cmdname string, args []string) {
var groupname, username string
var hallname, username string
var wildcard bool
var permissions stringOption
var doJSON bool
@@ -1164,7 +1164,7 @@ func updateUserCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "update the wildcard user")
cmd.Var(&permissions, "permissions", "permissions")
@@ -1175,7 +1175,7 @@ func updateUserCmd(cmdname string, args []string) {
os.Exit(1)
}
u, err := userURL(wildcard, groupname, username)
u, err := userURL(wildcard, hallname, username)
if err != nil {
log.Fatalf("Build URL: %v", err)
}
@@ -1217,13 +1217,13 @@ func updateUserCmd(cmdname string, args []string) {
}
func deleteUserCmd(cmdname string, args []string) {
var groupname, username string
var hallname, username string
var wildcard bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "delete the wildcard user")
cmd.Parse(args)
@@ -1233,9 +1233,9 @@ func deleteUserCmd(cmdname string, args []string) {
os.Exit(1)
}
if groupname == "" {
if hallname == "" {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
@@ -1250,12 +1250,12 @@ func deleteUserCmd(cmdname string, args []string) {
var err error
if wildcard {
u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
".wildcard-user",
)
} else {
u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname,
serverURL, "/skald-api/v0/.halls", hallname,
".users", username,
)
}
@@ -1270,13 +1270,13 @@ func deleteUserCmd(cmdname string, args []string) {
}
func showGroupCmd(cmdname string, args []string) {
var groupname string
var hallname string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname,
"%v [option...] %v\n",
os.Args[0], cmdname,
)
cmd.StringVar(&groupname, "group", "", "group `name`")
cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.Parse(args)
if cmd.NArg() != 0 {
@@ -1284,11 +1284,11 @@ func showGroupCmd(cmdname string, args []string) {
os.Exit(1)
}
if groupname == "" {
log.Fatal("Option \"-group\" is required.")
if hallname == "" {
log.Fatal("Option \"-hall\" is required.")
}
u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname)
u, err := url.JoinPath(serverURL, "/skald-api/v0/.halls/", hallname)
if err != nil {
log.Fatalf("Build URL: %v", err)
}
@@ -1296,7 +1296,7 @@ func showGroupCmd(cmdname string, args []string) {
var description map[string]any
_, err = getJSON(u, &description)
if err != nil {
log.Fatalf("Get group description: %v", err)
log.Fatalf("Get hall description: %v", err)
}
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
@@ -1315,7 +1315,7 @@ func listGroupsCmd(cmdname string, args []string) {
cmd.Parse(args)
patterns := cmd.Args()
u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/")
u, err := url.JoinPath(serverURL, "/skald-api/v0/.halls/")
if err != nil {
log.Fatalf("Build URL: %v", err)
}
@@ -1323,7 +1323,7 @@ func listGroupsCmd(cmdname string, args []string) {
var groups []string
_, err = getJSON(u, &groups)
if err != nil {
log.Fatalf("Get groups: %v", err)
log.Fatalf("Get halls: %v", err)
}
sort.Slice(groups, func(i, j int) bool {
return groups[i] < groups[j]
@@ -1343,13 +1343,13 @@ func listGroupsCmd(cmdname string, args []string) {
}
func listTokensCmd(cmdname string, args []string) {
var groupname stringOption
var hallname stringOption
var long bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.Var(&groupname, "group", "group `name`")
cmd.Var(&hallname, "hall", "hall `name`")
cmd.BoolVar(&long, "l", false, "display token fields")
cmd.Parse(args)
@@ -1358,14 +1358,14 @@ func listTokensCmd(cmdname string, args []string) {
os.Exit(1)
}
if !groupname.set {
if !hallname.set {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/",
serverURL, "/skald-api/v0/.halls/", hallname.value, ".tokens/",
)
if err != nil {
@@ -1430,14 +1430,14 @@ func listTokensCmd(cmdname string, args []string) {
}
func createTokenCmd(cmdname string, args []string) {
var groupname stringOption
var hallname stringOption
var username, permissions string
var includeSubgroups boolOption
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.Var(&groupname, "group", "group `name`")
cmd.Var(&hallname, "hall", "hall `name`")
cmd.Var(&includeSubgroups, "include-subgroups", "include subgroups")
cmd.StringVar(&username, "user", "", "encode user `name` in token")
cmd.StringVar(&permissions, "permissions", "present", "permissions")
@@ -1448,9 +1448,9 @@ func createTokenCmd(cmdname string, args []string) {
os.Exit(1)
}
if !groupname.set {
if !hallname.set {
fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n")
"Option \"-hall\" is required\n")
os.Exit(1)
}
@@ -1469,7 +1469,7 @@ func createTokenCmd(cmdname string, args []string) {
}
u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/",
serverURL, "/skald-api/v0/.halls/", hallname.value, ".tokens/",
)
if err != nil {
log.Fatalf("Build URL: %v", err)
@@ -1483,13 +1483,13 @@ func createTokenCmd(cmdname string, args []string) {
}
func revokeTokenCmd(cmdname string, args []string) {
var groupname stringOption
var hallname stringOption
var token string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.Var(&groupname, "group", "group `name`")
cmd.Var(&hallname, "hall", "hall `name`")
cmd.StringVar(&token, "token", "", "`token` to delete")
cmd.Parse(args)
@@ -1498,14 +1498,14 @@ func revokeTokenCmd(cmdname string, args []string) {
os.Exit(1)
}
if !groupname.set || token == "" {
if !hallname.set || token == "" {
fmt.Fprintf(cmd.Output(),
"Options \"-group\" and \"-token\" are required\n")
"Options \"-hall\" and \"-token\" are required\n")
os.Exit(1)
}
u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups/", groupname.value,
serverURL, "/skald-api/v0/.halls/", hallname.value,
".tokens", token,
)
if err != nil {
@@ -1522,13 +1522,13 @@ func revokeTokenCmd(cmdname string, args []string) {
}
func deleteTokenCmd(cmdname string, args []string) {
var groupname stringOption
var hallname stringOption
var token string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname,
)
cmd.Var(&groupname, "group", "group `name`")
cmd.Var(&hallname, "hall", "hall `name`")
cmd.StringVar(&token, "token", "", "`token` to delete")
cmd.Parse(args)
@@ -1537,14 +1537,14 @@ func deleteTokenCmd(cmdname string, args []string) {
os.Exit(1)
}
if !groupname.set || token == "" {
if !hallname.set || token == "" {
fmt.Fprintf(cmd.Output(),
"Options \"-group\" and \"-token\" are required\n")
"Options \"-hall\" and \"-token\" are required\n")
os.Exit(1)
}
u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups/", groupname.value,
serverURL, "/skald-api/v0/.halls/", hallname.value,
".tokens", token,
)
if err != nil {
+1 -1
View File
@@ -307,7 +307,7 @@ document.getElementById('start').onclick = async function(e) {
let button = /** @type{HTMLButtonElement} */(this);
button.hidden = true;
try {
await start("/group/public/");
await start("/hall/public/");
} catch(e) {
displayError(e);
};
+7 -7
View File
@@ -15,22 +15,22 @@
<div class="home">
<h1 id="title" class="navbar-brand">Skald</h1>
<form id="groupform">
<label for="group">Group:</label>
<input id="group" type="text" name="group" class="form-control form-control-inline" autofocus/>
<form id="hallform">
<label for="hall">Hall:</label>
<input id="hall" type="text" name="hall" class="form-control form-control-inline" autofocus/>
<input id='submitbutton' type="submit" value="Join" class="btn btn-default btn-large"/><br/>
</form>
<p id="errormessage"></p>
<div id="public-groups" class="groups">
<h2>Public groups</h2>
<div id="public-halls" class="groups">
<h2>Public halls</h2>
<table id="public-groups-table"></table>
<table id="public-halls-table"></table>
</div>
</div>
<footer class="signature">
<p><a href="https://skald.org/">Skald</a> by <a href="https://www.irif.fr/~jch/" rel="author">Juliusz Chroboczek</a></p>
<p>Skald is derived from <a href="https://galene.org/">Galene</a> by <a href="https://www.irif.fr/~jch/" rel="author">Juliusz Chroboczek</a></p>
</footer>
<script src="/mainpage.js" defer></script>
+1 -1
View File
@@ -26,7 +26,7 @@ body {
padding: 1.875rem;
}
#public-groups-table tr a{
#public-halls-table tr a{
margin-left: 0.9375rem;
font-weight: 700;
}
+10 -10
View File
@@ -20,16 +20,16 @@
'use strict';
document.getElementById('groupform').onsubmit = async function(e) {
document.getElementById('hallform').onsubmit = async function(e) {
e.preventDefault();
clearError();
let groupinput = document.getElementById('group')
let groupinput = document.getElementById('hall')
let button = document.getElementById('submitbutton');
let group = groupinput.value.trim();
if(group === '')
return;
let url = '/group/' + group + '/';
let url = '/hall/' + group + '/';
let statusUrl = url + '.status.json';
try {
@@ -78,25 +78,25 @@ function clearError() {
}
}
async function listPublicGroups() {
let div = document.getElementById('public-groups');
let table = document.getElementById('public-groups-table');
async function listPublicHalls() {
let div = document.getElementById('public-halls');
let table = document.getElementById('public-halls-table');
let l;
try {
let r = await fetch('/public-groups.json');
let r = await fetch('/public-halls.json');
if(!r.ok)
throw new Error(`${r.status} ${r.statusText}`);
l = await r.json();
} catch(e) {
table.textContent = `Couldn't fetch groups: ${e}`;
table.textContent = `Couldn't fetch halls: ${e}`;
div.classList.remove('nogroups');
div.classList.add('groups');
return;
}
if (l.length === 0) {
table.textContent = '(No groups found.)';
table.textContent = '(No halls found.)';
div.classList.remove('groups');
div.classList.add('nogroups');
return;
@@ -131,4 +131,4 @@ async function listPublicGroups() {
}
listPublicGroups();
listPublicHalls();
+14 -14
View File
@@ -159,7 +159,7 @@ async function updateObject(url, values, etag) {
* @returns {Promise<Array<string>>}
*/
async function listGroups() {
return await listObjects('/skald-api/v0/.groups/');
return await listObjects('/skald-api/v0/.halls/');
}
/**
@@ -170,7 +170,7 @@ async function listGroups() {
* @returns {Promise<Object>}
*/
async function getGroup(group, etag) {
return await getObject(`/skald-api/v0/.groups/${group}`, etag);
return await getObject(`/skald-api/v0/.halls/${group}`, etag);
}
/**
@@ -180,7 +180,7 @@ async function getGroup(group, etag) {
* @param {Object} [values]
*/
async function createGroup(group, values) {
return await createObject(`/skald-api/v0/.groups/${group}`, values);
return await createObject(`/skald-api/v0/.halls/${group}`, values);
}
/**
@@ -190,11 +190,11 @@ async function createGroup(group, values) {
* @param {string} [etag]
*/
async function deleteGroup(group, etag) {
return await deleteObject(`/skald-api/v0/.groups/${group}`, etag);
return await deleteObject(`/skald-api/v0/.halls/${group}`, etag);
}
/**
* updateGroup modifies a group definition.
* updateGroup modifies a hall definition.
* Any fields present in values are overriden, any fields absent in values
* are left unchanged.
*
@@ -203,7 +203,7 @@ async function deleteGroup(group, etag) {
* @param {string} [etag]
*/
async function updateGroup(group, values, etag) {
return await updateObject(`/skald-api/v0/.groups/${group}`, values);
return await updateObject(`/skald-api/v0/.halls/${group}`, values);
}
/**
@@ -213,7 +213,7 @@ async function updateGroup(group, values, etag) {
* @returns {Promise<Array<string>>}
*/
async function listUsers(group) {
return await listObjects(`/skald-api/v0/.groups/${group}/.users/`);
return await listObjects(`/skald-api/v0/.halls/${group}/.users/`);
}
/**
@@ -225,11 +225,11 @@ async function listUsers(group) {
*/
function userURL(group, user, wildcard) {
if(wildcard)
return `/skald-api/v0/.groups/${group}/.wildcard-user`;
return `/skald-api/v0/.halls/${group}/.wildcard-user`;
else if(user === "")
return `/skald-api/v0/.groups/${group}/.empty-user`;
return `/skald-api/v0/.halls/${group}/.empty-user`;
else
return `/skald-api/v0/.groups/${group}/.users/${user}`
return `/skald-api/v0/.halls/${group}/.users/${user}`
}
/**
@@ -320,7 +320,7 @@ async function setPassword(group, user, wildcard, password, oldpassword) {
* @returns {Promise<Array<string>>}
*/
async function listTokens(group) {
return await listObjects(`/skald-api/v0/.groups/${group}/.tokens/`);
return await listObjects(`/skald-api/v0/.halls/${group}/.tokens/`);
}
/**
@@ -332,7 +332,7 @@ async function listTokens(group) {
* @returns {Promise<Object>}
*/
async function getToken(group, token, etag) {
return await getObject(`/skald-api/v0/.groups/${group}/.tokens/${token}`,
return await getObject(`/skald-api/v0/.halls/${group}/.tokens/${token}`,
etag);
}
@@ -353,7 +353,7 @@ async function createToken(group, template) {
}
let r = await fetch(
`/skald-api/v0/.groups/${group}/.tokens/`,
`/skald-api/v0/.halls/${group}/.tokens/`,
options);
if(!r.ok)
throw httpError(r);
@@ -373,6 +373,6 @@ async function updateToken(group, token, etag) {
if(!token.token)
throw new Error("Unnamed token");
return await updateObject(
`/skald-api/v0/.groups/${group}/.tokens/${token.token}`,
`/skald-api/v0/.halls/${group}/.tokens/${token.token}`,
token, etag);
}
+9 -9
View File
@@ -83,20 +83,20 @@ X Search for remaining Galene names and classify each survivor as attribution/hi
# PHASE 3: URLS, API, PROTOCOL, AND CONTROL TOOL
# ============================================================================
- Rename browser hall URLs from /group/name/ to /hall/name/
- Rename /public-groups.json to /public-halls.json
- Rename /galene-api/ to /skald-api/
- Rename API collection paths from .groups to .halls
X Rename browser hall URLs from /group/name/ to /hall/name/
X Rename /public-groups.json to /public-halls.json
X Rename /galene-api/ to /skald-api/
X Rename API collection paths from .groups to .halls
- Rename protocol join fields from group to hall
- Rename protocol callbacks/events from joined group semantics to joined hall semantics
- Update static/protocol.js public API: ServerConnection.join and related callbacks
- Update static/management.js to use /skald-api/v0/.halls/
- Update static/mainpage.js to open /hall/ URLs and fetch /public-halls.json
X Update static/management.js to use /skald-api/v0/.halls/
X Update static/mainpage.js to open /hall/ URLs and fetch /public-halls.json
- Update stats endpoint naming only if it exposes Skald/group terminology
- Update skaldctl command names: create-hall, update-hall, delete-hall, list-halls
X Update skaldctl command names: create-hall, update-hall, delete-hall, list-halls
- Update skaldctl flags: -hall, -include-subhalls, -auto-subhalls, etc.
- Update skaldctl help text and error messages
- Update API and webserver tests for /hall/ and /skald-api/
X Update skaldctl help text and error messages
X Update API and webserver tests for /hall/ and /skald-api/
- Verify old /group/, /public-groups.json, /galene-api/, and .groups paths fail clearly
- Verify protocol backward compatibility with Skald is intentionally broken
+3 -3
View File
@@ -190,10 +190,10 @@ func parseJWT(token string, keys []map[string]any) (*JWT, error) {
func matchGroup(pth, group string, includeSubgroups bool) bool {
if !includeSubgroups {
return pth == "/group/"+group+"/"
return pth == "/hall/"+group+"/"
}
if !strings.HasPrefix(pth, "/group/") {
if !strings.HasPrefix(pth, "/hall/") {
return false
}
@@ -201,7 +201,7 @@ func matchGroup(pth, group string, includeSubgroups bool) bool {
return false
}
return strings.HasPrefix("/group/"+group+"/", pth)
return strings.HasPrefix("/hall/"+group+"/", pth)
}
func (token *JWT) Check(host, group string, username *string) (string, []string, error) {
+13 -13
View File
@@ -85,21 +85,21 @@ func TestMatchGroup(t *testing.T) {
s bool
}
good := []tt{
{"/group/a/", "a", false},
{"/group/a/b/", "a/b", false},
{"/group/a/", "a", true},
{"/group/a/", "a/b", true},
{"/group/a/b/", "a/b", true},
{"/group/a/b/", "a/b/c", true},
{"/hall/a/", "a", false},
{"/hall/a/b/", "a/b", false},
{"/hall/a/", "a", true},
{"/hall/a/", "a/b", true},
{"/hall/a/b/", "a/b", true},
{"/hall/a/b/", "a/b/c", true},
}
bad := []tt{
{"/group/a/", "b", false},
{"/group/a", "a", false},
{"/group/a", "a", true},
{"/group/a/", "a/b", false},
{"/group/a/b/", "a", false},
{"/group/a/b/", "a", true},
{"/hall/a/", "b", false},
{"/hall/a", "a", false},
{"/hall/a", "a", true},
{"/hall/a/", "a/b", false},
{"/hall/a/b/", "a", false},
{"/hall/a/b/", "a", true},
}
for _, test := range good {
@@ -142,7 +142,7 @@ func TestJWT(t *testing.T) {
john := "john"
jack := "jack"
baseClaims := jwt.MapClaims{
"aud": "https://skald.org:8443/group/auth/",
"aud": "https://skald.org:8443/hall/auth/",
"permissions": []string{"present"},
"iat": int64(1645310294),
"exp": int64(2906750294),
+16 -16
View File
@@ -46,27 +46,27 @@ func TestStatefulCheck(t *testing.T) {
user2 := "user2"
token1 := &Stateful{
Token: "token",
Group: "group",
Group: "hall",
Username: &user,
Permissions: []string{"present", "message"},
Expires: &future,
}
token2 := &Stateful{
Token: "token",
Group: "group",
Group: "hall",
Permissions: []string{"present", "message"},
Expires: &future,
}
token3 := &Stateful{
Token: "token",
Group: "group",
Group: "hall",
Username: &user,
Permissions: []string{"present", "message"},
Expires: &past,
}
token4 := &Stateful{
Token: "token",
Group: "group",
Group: "hall",
Username: &user,
Permissions: []string{"present", "message"},
Expires: &future,
@@ -74,7 +74,7 @@ func TestStatefulCheck(t *testing.T) {
}
token5 := &Stateful{
Token: "token",
Group: "group",
Group: "hall",
IncludeSubgroups: true,
Username: &user,
Permissions: []string{"present", "message"},
@@ -106,48 +106,48 @@ func TestStatefulCheck(t *testing.T) {
}{
{
token: token1,
group: "group",
group: "hall",
username: &user,
expUsername: user,
expPermissions: []string{"present", "message"},
},
{
token: token1,
group: "group",
group: "hall",
username: &user2,
expUsername: user,
expPermissions: []string{"present", "message"},
},
{
token: token1,
group: "group",
group: "hall",
expUsername: user,
expPermissions: []string{"present", "message"},
},
{
token: token2,
group: "group",
group: "hall",
username: &user,
expUsername: "",
expPermissions: []string{"present", "message"},
},
{
token: token5,
group: "group",
group: "hall",
username: &user,
expUsername: "user",
expPermissions: []string{"present", "message"},
},
{
token: token5,
group: "group/subgroup",
group: "hall/subhall",
username: &user,
expUsername: "user",
expPermissions: []string{"present", "message"},
},
{
token: token7,
group: "group/subgroup",
group: "hall/subhall",
username: &user,
expUsername: "user",
expPermissions: []string{"present", "message"},
@@ -171,17 +171,17 @@ func TestStatefulCheck(t *testing.T) {
}{
{
token: token1,
group: "group2",
group: "hall2",
username: &user,
},
{
token: token3,
group: "group",
group: "hall",
username: &user,
},
{
token: token4,
group: "group",
group: "hall",
username: &user,
},
{
@@ -196,7 +196,7 @@ func TestStatefulCheck(t *testing.T) {
},
{
token: token6,
group: "group",
group: "hall",
username: &user,
},
{
+2 -2
View File
@@ -25,7 +25,7 @@ func TestToken(t *testing.T) {
user := "user"
_, err = Update(&Stateful{
Token: "token",
Group: "group",
Group: "hall",
Username: &user,
Permissions: []string{"present"},
Expires: &future,
@@ -39,7 +39,7 @@ func TestToken(t *testing.T) {
t.Fatalf("Parse: %v", err)
}
_, _, err = token.Check("skald.org:8443", "group", &user)
_, _, err = token.Check("skald.org:8443", "hall", &user)
if err != nil {
t.Errorf("Check: %v", err)
}
+4 -4
View File
@@ -34,7 +34,7 @@ func checkAdmin(w http.ResponseWriter, r *http.Request) bool {
// checkPasswordAdmin checks whether the client authentifies as either an
// administrator or the given user. It is used to check whether the
// client has the right to change user's password.
func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, groupname, user string, wildcard bool) bool {
func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, hallname, user string, wildcard bool) bool {
username, password, ok := r.BasicAuth()
if ok {
ok, err := adminMatch(username, password)
@@ -47,11 +47,11 @@ func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, groupname, user
}
}
if ok && !wildcard && username == user {
desc, err := group.GetDescription(groupname)
desc, err := group.GetDescription(hallname)
if err != nil {
internalError(w,
"Get description for group %v: %v",
groupname, err,
hallname, err,
)
return false
}
@@ -169,7 +169,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("cache-control", "no-cache")
sendJSON(w, r, stats.GetGroups())
case ".groups":
case ".halls":
apiGroupHandler(w, r, rest)
default:
http.NotFound(w, r)
+59 -59
View File
@@ -115,58 +115,58 @@ func TestApi(t *testing.T) {
}
var groups []string
err = getJSON("/skald-api/v0/.groups/", &groups)
err = getJSON("/skald-api/v0/.halls/", &groups)
if err != nil || len(groups) != 0 {
t.Errorf("Get groups: %v", err)
t.Errorf("Get halls: %v", err)
}
resp, err := do("PUT", "/skald-api/v0/.groups/test/",
resp, err := do("PUT", "/skald-api/v0/.halls/test/",
"application/json", "\"foo\"", "",
"{}")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode)
t.Errorf("Create hall (bad ETag): %v %v", err, resp.StatusCode)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/",
resp, err = do("PUT", "/skald-api/v0/.halls/test/",
"text/plain", "", "",
"Hello, world!")
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
t.Errorf("Create group (bad content-type): %v %v",
t.Errorf("Create hall (bad content-type): %v %v",
err, resp.StatusCode)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/",
resp, err = do("PUT", "/skald-api/v0/.halls/test/",
"application/json", "", "*",
"{}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create group: %v %v", err, resp.StatusCode)
t.Errorf("Create hall: %v %v", err, resp.StatusCode)
}
var desc *group.Description
err = getJSON("/skald-api/v0/.groups/test/", &desc)
err = getJSON("/skald-api/v0/.halls/test/", &desc)
if err != nil || len(desc.Users) != 0 {
t.Errorf("Get group: %v", err)
t.Errorf("Get hall: %v", err)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/",
resp, err = do("PUT", "/skald-api/v0/.halls/test/",
"application/json", "", "*",
"{}")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode)
t.Errorf("Create hall (bad ETag): %v %v", err, resp.StatusCode)
}
resp, err = do("DELETE", "/skald-api/v0/.groups/test/",
resp, err = do("DELETE", "/skald-api/v0/.halls/test/",
"", "", "*", "")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode)
t.Errorf("Delete hall (bad ETag): %v %v", err, resp.StatusCode)
}
err = getJSON("/skald-api/v0/.groups/", &groups)
err = getJSON("/skald-api/v0/.halls/", &groups)
if err != nil || len(groups) != 1 || groups[0] != "test" {
t.Errorf("Get groups: %v %v", err, groups)
t.Errorf("Get halls: %v %v", err, groups)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/.keys",
resp, err = do("PUT", "/skald-api/v0/.halls/test/.keys",
"application/jwk-set+json", "", "",
`{"keys": [{
"kty": "oct", "alg": "HS256",
@@ -176,12 +176,12 @@ func TestApi(t *testing.T) {
t.Errorf("Set key: %v %v", err, resp.StatusCode)
}
err = getJSON("/skald-api/v0/.groups/test/.users/", &groups)
err = getJSON("/skald-api/v0/.halls/test/.users/", &groups)
if err != nil || len(groups) != 0 {
t.Errorf("Get users: %v", err)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
resp, err = do("PUT", "/skald-api/v0/.halls/test/.users/jch",
"text/plain", "", "*",
`hello, world!`)
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
@@ -189,7 +189,7 @@ func TestApi(t *testing.T) {
err, resp.StatusCode)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
resp, err = do("PUT", "/skald-api/v0/.halls/test/.users/jch",
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusCreated {
@@ -197,26 +197,26 @@ func TestApi(t *testing.T) {
}
var users []string
err = getJSON("/skald-api/v0/.groups/test/.users/", &users)
err = getJSON("/skald-api/v0/.halls/test/.users/", &users)
if err != nil || len(users) != 1 || users[0] != "jch" {
t.Errorf("Get users: %v %v", err, users)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
resp, err = do("PUT", "/skald-api/v0/.halls/test/.users/jch",
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create user (bad ETag): %v %v", err, resp.StatusCode)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch/.password",
resp, err = do("PUT", "/skald-api/v0/.halls/test/.users/jch/.password",
"application/json", "", "",
`"toto"`)
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Set password (PUT): %v %v", err, resp.StatusCode)
}
resp, err = do("POST", "/skald-api/v0/.groups/test/.users/jch/.password",
resp, err = do("POST", "/skald-api/v0/.halls/test/.users/jch/.password",
"text/plain", "", "",
`toto`)
if err != nil || resp.StatusCode != http.StatusNoContent {
@@ -224,7 +224,7 @@ func TestApi(t *testing.T) {
}
var user group.UserDescription
err = getJSON("/skald-api/v0/.groups/test/.users/jch", &user)
err = getJSON("/skald-api/v0/.halls/test/.users/jch", &user)
if err != nil {
t.Errorf("Get user: %v", err)
}
@@ -245,10 +245,10 @@ func TestApi(t *testing.T) {
t.Errorf("Password.Type: %v", desc.Users["jch"].Password.Type)
}
resp, err = do("DELETE", "/skald-api/v0/.groups/test/.users/jch",
resp, err = do("DELETE", "/skald-api/v0/.halls/test/.users/jch",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete group: %v %v", err, resp.StatusCode)
t.Errorf("Delete hall: %v %v", err, resp.StatusCode)
}
desc, err = group.GetDescription("test")
@@ -260,14 +260,14 @@ func TestApi(t *testing.T) {
t.Errorf("Users (after delete): %#v", desc.Users)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test/.wildcard-user",
resp, err = do("PUT", "/skald-api/v0/.halls/test/.wildcard-user",
"application/json", "", "*",
`{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create wildcard user: %v %v", err, resp.StatusCode)
}
err = getJSON("/skald-api/v0/.groups/test/.wildcard-user", &user)
err = getJSON("/skald-api/v0/.halls/test/.wildcard-user", &user)
if err != nil {
t.Errorf("Get wildcard user: %v", err)
}
@@ -281,7 +281,7 @@ func TestApi(t *testing.T) {
t.Errorf("Got %v, expected %v", desc.WildcardUser, user)
}
resp, err = do("DELETE", "/skald-api/v0/.groups/test/.wildcard-user",
resp, err = do("DELETE", "/skald-api/v0/.halls/test/.wildcard-user",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete wildcard user: %v %v", err, resp.StatusCode)
@@ -300,33 +300,33 @@ func TestApi(t *testing.T) {
t.Errorf("Keys: %v", len(desc.AuthKeys))
}
resp, err = do("POST", "/skald-api/v0/.groups/test/.tokens/",
resp, err = do("POST", "/skald-api/v0/.halls/test/.tokens/",
"application/json", "", "", `{"group":"bad"}`)
if err != nil || resp.StatusCode != http.StatusBadRequest {
t.Errorf("Create token (bad group): %v %v", err, resp.StatusCode)
}
resp, err = do("POST", "/skald-api/v0/.groups/test/.tokens/",
resp, err = do("POST", "/skald-api/v0/.halls/test/.tokens/",
"application/json", "", "", "{}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create token: %v %v", err, resp.StatusCode)
}
resp, err = do("POST", "/skald-api/v0/.groups/.tokens/",
resp, err = do("POST", "/skald-api/v0/.halls/.tokens/",
"application/json", "", "", "{\"includeSubgroups\": true}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create global token: %v %v", err, resp.StatusCode)
}
var toknames []string
err = getJSON("/skald-api/v0/.groups/test/.tokens/", &toknames)
err = getJSON("/skald-api/v0/.halls/test/.tokens/", &toknames)
if err != nil || len(toknames) != 1 {
t.Errorf("Get tokens: %v %v", err, toknames)
}
tokname := toknames[0]
var globalToknames []string
err = getJSON("/skald-api/v0/.groups/test/.tokens/", &globalToknames)
err = getJSON("/skald-api/v0/.halls/test/.tokens/", &globalToknames)
if err != nil || len(globalToknames) != 1 {
t.Errorf("Get global tokens: %v %v", err, globalToknames)
}
@@ -336,7 +336,7 @@ func TestApi(t *testing.T) {
t.Errorf("token.List: %v %v", tokens, err)
}
tokenpath := "/skald-api/v0/.groups/test/.tokens/" + tokname
tokenpath := "/skald-api/v0/.halls/test/.tokens/" + tokname
var tok token.Stateful
err = getJSON(tokenpath, &tok)
if err != nil {
@@ -375,13 +375,13 @@ func TestApi(t *testing.T) {
t.Errorf("Got %v, expected %v (%v)", tok.Expires, e, err)
}
resp, err = do("PUT", "/skald-api/v0/.groups/test2",
resp, err = do("PUT", "/skald-api/v0/.halls/test2",
"application/json", "", "*", "{}")
if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create test2: %v %v", err, resp.StatusCode)
}
tokenpath2 := "/skald-api/v0/.groups/test2/.tokens/" + tokname
tokenpath2 := "/skald-api/v0/.halls/test2/.tokens/" + tokname
resp, err = do("GET", tokenpath2, "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNotFound {
t.Errorf("Get token in bad group: %v %v", err, resp.StatusCode)
@@ -402,16 +402,16 @@ func TestApi(t *testing.T) {
t.Errorf("Token list: %v %v", tokens, err)
}
resp, err = do("DELETE", "/skald-api/v0/.groups/test/.keys",
resp, err = do("DELETE", "/skald-api/v0/.halls/test/.keys",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete keys: %v %v", err, resp.StatusCode)
}
resp, err = do("DELETE", "/skald-api/v0/.groups/test/",
resp, err = do("DELETE", "/skald-api/v0/.halls/test/",
"", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete group: %v %v", err, resp.StatusCode)
t.Errorf("Delete hall: %v %v", err, resp.StatusCode)
}
_, err = group.GetDescription("test")
@@ -448,8 +448,8 @@ func TestApiBadAuth(t *testing.T) {
}
do("GET", "/skald-api/v0/.stats")
do("GET", "/skald-api/v0/.groups/")
do("PUT", "/skald-api/v0/.groups/test/")
do("GET", "/skald-api/v0/.halls/")
do("PUT", "/skald-api/v0/.halls/test/")
f, err := os.Create(filepath.Join(group.Directory, "test.json"))
if err != nil {
@@ -460,20 +460,20 @@ func TestApiBadAuth(t *testing.T) {
}\n`)
f.Close()
do("PUT", "/skald-api/v0/.groups/test/")
do("DELETE", "/skald-api/v0/.groups/test/")
do("GET", "/skald-api/v0/.groups/test/.users/")
do("GET", "/skald-api/v0/.groups/test/.users/jch")
do("GET", "/skald-api/v0/.groups/test/.users/jch")
do("PUT", "/skald-api/v0/.groups/test/.users/jch")
do("DELETE", "/skald-api/v0/.groups/test/.users/jch")
do("GET", "/skald-api/v0/.groups/test/.users/not-jch")
do("PUT", "/skald-api/v0/.groups/test/.users/not-jch")
do("PUT", "/skald-api/v0/.groups/test/.users/jch/.password")
do("POST", "/skald-api/v0/.groups/test/.users/jch/.password")
do("GET", "/skald-api/v0/.groups/test/.tokens/")
do("POST", "/skald-api/v0/.groups/test/.tokens/")
do("GET", "/skald-api/v0/.groups/test/.tokens/token")
do("PUT", "/skald-api/v0/.groups/test/.tokens/token")
do("DELETE", "/skald-api/v0/.groups/test/.tokens/token")
do("PUT", "/skald-api/v0/.halls/test/")
do("DELETE", "/skald-api/v0/.halls/test/")
do("GET", "/skald-api/v0/.halls/test/.users/")
do("GET", "/skald-api/v0/.halls/test/.users/jch")
do("GET", "/skald-api/v0/.halls/test/.users/jch")
do("PUT", "/skald-api/v0/.halls/test/.users/jch")
do("DELETE", "/skald-api/v0/.halls/test/.users/jch")
do("GET", "/skald-api/v0/.halls/test/.users/not-jch")
do("PUT", "/skald-api/v0/.halls/test/.users/not-jch")
do("PUT", "/skald-api/v0/.halls/test/.users/jch/.password")
do("POST", "/skald-api/v0/.halls/test/.users/jch/.password")
do("GET", "/skald-api/v0/.halls/test/.tokens/")
do("POST", "/skald-api/v0/.halls/test/.tokens/")
do("GET", "/skald-api/v0/.halls/test/.tokens/token")
do("PUT", "/skald-api/v0/.halls/test/.tokens/token")
do("DELETE", "/skald-api/v0/.halls/test/.tokens/token")
}
+8 -8
View File
@@ -35,7 +35,7 @@ var Insecure bool
func Serve(address string, dataDir string) error {
http.Handle("/", &fileHandler{http.Dir(StaticRoot)})
http.HandleFunc("/group/", groupHandler)
http.HandleFunc("/hall/", groupHandler)
http.HandleFunc("/recordings",
func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r,
@@ -43,7 +43,7 @@ func Serve(address string, dataDir string) error {
})
http.HandleFunc("/recordings/", recordingsHandler)
http.HandleFunc("/ws", wsHandler)
http.HandleFunc("/public-groups.json", publicHandler)
http.HandleFunc("/public-halls.json", publicHandler)
http.HandleFunc("/skald-api/", apiHandler)
s := &http.Server{
@@ -346,7 +346,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
return
}
name := parseGroupName("/group/", r.URL.Path)
name := parseGroupName("/hall/", r.URL.Path)
if name == "" {
notFound(w)
return
@@ -358,8 +358,8 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
return
}
if r.URL.Path != "/group/"+name+"/" {
http.Redirect(w, r, "/group/"+name+"/",
if r.URL.Path != "/hall/"+name+"/" {
http.Redirect(w, r, "/hall/"+name+"/",
http.StatusPermanentRedirect)
return
}
@@ -415,7 +415,7 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) {
internalError(w, "groupStatusHandler: this shouldn't happen")
return
}
name := parseGroupName("/group/", pth)
name := parseGroupName("/hall/", pth)
if name == "" {
notFound(w)
return
@@ -705,13 +705,13 @@ func handleGroupAction(w http.ResponseWriter, r *http.Request, group string) {
}
}
func checkGroupPermissions(w http.ResponseWriter, r *http.Request, groupname string) bool {
func checkGroupPermissions(w http.ResponseWriter, r *http.Request, hallname string) bool {
user, pass, ok := r.BasicAuth()
if !ok {
return false
}
g := group.Get(groupname)
g := group.Get(hallname)
if g == nil {
return false
}
+8 -8
View File
@@ -19,17 +19,17 @@ func TestParseGroupName(t *testing.T) {
{"/foo", ""},
{"foo", ""},
{"group/foo", ""},
{"/group", ""},
{"/group/..", ""},
{"/group/foo/../bar", "bar"},
{"/group/foo", "foo"},
{"/group/foo/", "foo"},
{"/group/foo/bar", "foo/bar"},
{"/group/foo/bar/", "foo/bar"},
{"/hall", ""},
{"/hall/..", ""},
{"/hall/foo/../bar", "bar"},
{"/hall/foo", "foo"},
{"/hall/foo/", "foo"},
{"/hall/foo/bar", "foo/bar"},
{"/hall/foo/bar/", "foo/bar"},
}
for _, pg := range a {
g := parseGroupName("/group/", pg.p)
g := parseGroupName("/hall/", pg.p)
if g != pg.g {
t.Errorf("Path %v, got %v, expected %v",
pg.p, g, pg.g)
+2 -2
View File
@@ -153,7 +153,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) {
return
}
name := parseGroupName("/group/", pth)
name := parseGroupName("/hall/", pth)
if name == "" {
notFound(w)
return
@@ -269,7 +269,7 @@ func whipResourceHandler(w http.ResponseWriter, r *http.Request) {
return
}
name := parseGroupName("/group/", pth)
name := parseGroupName("/hall/", pth)
if name == "" {
notFound(w)
return