One-view tokens: local records in linked zones, zone lookup, view list

A token for one view of a linked zone (an ACME token for external)
couldn't add records: a new record was shared and reached the other
view. A new name and type is now local when the user may not change it
in the other views. A zone name in several views resolves to the only
one the user can see, and the error no longer names views they can't.
GET /views lists the views a user sees zones in (name, colour, count;
limited) without views:read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-18 15:57:06 +01:00
1 parent 4cc2ab2287
commit 57012fece8
13 files changed
+159 -12

No files matched your search

+31
View File
@@ -49,3 +49,34 @@ func TestZoneInViews(t *testing.T) {
t.Errorf("in_views = %v", z["in_views"])
}
}
// A token for records in one view of a zone that is in two: the zone is
// found without naming the view, and view list names the views it can
// see zones in, without their settings.
func TestOneViewToken(t *testing.T) {
e := newAPIEnv(t)
e.do("POST", "/views", e.token, map[string]any{"name": "internal", "match_clients": []string{"10.0.0.0/8"}}, 201)
e.do("POST", "/views", e.token, map[string]any{"name": "external"}, 201)
for _, v := range []string{"internal", "external"} {
e.do("POST", "/zones", e.token, map[string]any{"name": "adbraun.lol", "view": v, "nameservers": []string{"ns1.example.net."}}, 201)
}
e.do("POST", "/users", e.token, map[string]any{"username": "certbot", "password": "", "grant_strings": []string{"records:write zones=adbraun.lol views=external names=_acme-challenge.* types=TXT"}}, 201)
var uid int64
for _, u := range e.list("/users", e.token) {
if u["username"] == "certbot" {
uid = int64(u["id"].(float64))
}
}
tok := e.do("POST", "/users/"+itoa(uid)+"/tokens", e.token, map[string]any{"name": "certbot"}, 201)["token"].(string)
if z := e.do("GET", "/zones/adbraun.lol", tok, nil, 200); z["view"] != "external" {
t.Errorf("zone without a view: %v", z["view"])
}
e.do("GET", "/zones/adbraun.lol/records", tok, nil, 200)
views := e.list("/views", tok)
if len(views) != 1 || views[0]["name"] != "external" || views[0]["limited"] != true || views[0]["match_clients"] != nil {
t.Errorf("views = %v", views)
}
// The admin still has to say which.
e.do("GET", "/zones/adbraun.lol", e.token, nil, 400)
}