From 8727d6bacccfc5d14d75dcafa323f46f3ca8a7a7 Mon Sep 17 00:00:00 2001 From: Steve Cliff Date: Tue, 5 May 2026 09:09:01 +0100 Subject: [PATCH] http: roleAtLeast helper for the role hierarchy --- internal/server/http/rbac.go | 26 ++++++++++++++++++++++++ internal/server/http/rbac_test.go | 33 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 internal/server/http/rbac.go create mode 100644 internal/server/http/rbac_test.go diff --git a/internal/server/http/rbac.go b/internal/server/http/rbac.go new file mode 100644 index 0000000..7068f0d --- /dev/null +++ b/internal/server/http/rbac.go @@ -0,0 +1,26 @@ +package http + +import ( + "gitea.dcglab.co.uk/steve/restic-manager/internal/store" +) + +// rank maps each role to a numeric tier so 'A is at least B' becomes +// 'rank[A] >= rank[B] && both are known'. Unknown roles return 0 → +// fail-closed against either argument. +var roleRank = map[store.Role]int{ + store.RoleViewer: 1, + store.RoleOperator: 2, + store.RoleAdmin: 3, +} + +// roleAtLeast reports whether `have` meets or exceeds `min` in the +// admin > operator > viewer hierarchy. Either side being an unknown +// role returns false. +func roleAtLeast(have, min store.Role) bool { + h, hok := roleRank[have] + m, mok := roleRank[min] + if !hok || !mok { + return false + } + return h >= m +} diff --git a/internal/server/http/rbac_test.go b/internal/server/http/rbac_test.go new file mode 100644 index 0000000..5b20b3d --- /dev/null +++ b/internal/server/http/rbac_test.go @@ -0,0 +1,33 @@ +package http + +import ( + "testing" + + "gitea.dcglab.co.uk/steve/restic-manager/internal/store" +) + +func TestRoleAtLeast(t *testing.T) { + t.Parallel() + cases := []struct { + have store.Role + min store.Role + want bool + }{ + {store.RoleViewer, store.RoleViewer, true}, + {store.RoleOperator, store.RoleViewer, true}, + {store.RoleAdmin, store.RoleViewer, true}, + {store.RoleAdmin, store.RoleOperator, true}, + {store.RoleAdmin, store.RoleAdmin, true}, + {store.RoleViewer, store.RoleOperator, false}, + {store.RoleViewer, store.RoleAdmin, false}, + {store.RoleOperator, store.RoleAdmin, false}, + {store.Role("nonsense"), store.RoleViewer, false}, + {store.RoleAdmin, store.Role("nonsense"), false}, + } + for _, c := range cases { + got := roleAtLeast(c.have, c.min) + if got != c.want { + t.Errorf("have=%q min=%q: got %v want %v", c.have, c.min, got, c.want) + } + } +}