Files
photoprism/internal/api/cluster_permissions_test.go
2025-09-26 06:36:23 +02:00

97 lines
3.1 KiB
Go

package api
import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/service/cluster"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestClusterPermissions(t *testing.T) {
t.Run("UnauthorizedWhenPublicDisabled", func(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().NodeRole = cluster.RolePortal
// Disable public mode so Auth requires a session.
conf.SetAuthMode(config.AuthModePasswd)
defer conf.SetAuthMode(config.AuthModePublic)
ClusterSummary(router)
ClusterMetrics(router)
r := PerformRequest(app, http.MethodGet, "/api/v1/cluster")
assert.Equal(t, http.StatusUnauthorized, r.Code)
r = PerformRequest(app, http.MethodGet, "/api/v1/cluster/metrics")
assert.Equal(t, http.StatusUnauthorized, r.Code)
})
t.Run("ForbiddenFromCDN", func(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().NodeRole = cluster.RolePortal
ClusterListNodes(router)
ClusterMetrics(router)
req, _ := http.NewRequest(http.MethodGet, "/api/v1/cluster/nodes", nil)
// Mark as CDN request, which Auth() forbids.
req.Header.Set("Cdn-Host", "edge.example")
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("AdminCanAccess", func(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().NodeRole = cluster.RolePortal
ClusterSummary(router)
ClusterMetrics(router)
token := AuthenticateAdmin(app, router)
r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster", token)
assert.Equal(t, http.StatusOK, r.Code)
r = AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster/metrics", token)
assert.Equal(t, http.StatusOK, r.Code)
})
// Note: most fixture users have admin role; client-scope test below covers non-admin denial.
t.Run("ClientInsufficientScope", func(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().NodeRole = cluster.RolePortal
conf.SetAuthMode(config.AuthModePasswd)
defer conf.SetAuthMode(config.AuthModePublic)
// Acquire client token with metrics scope (does not include cluster).
OAuthToken(router)
data := url.Values{
"grant_type": {authn.GrantClientCredentials.String()},
"client_id": {"cs5cpu17n6gj2qo5"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
}
req, _ := http.NewRequest(http.MethodPost, "/api/v1/oauth/token", strings.NewReader(data.Encode()))
req.Header.Add(header.ContentType, header.ContentTypeForm)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
token := gjson.Get(w.Body.String(), "access_token").String()
ClusterSummary(router)
ClusterMetrics(router)
r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster", token)
assert.Equal(t, http.StatusForbidden, r.Code)
r = AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster/metrics", token)
assert.Equal(t, http.StatusForbidden, r.Code)
})
}