test: expect the missing scope or permission in 403 bodies
This commit is contained in:
88
middleware/api_token_test.go
Normal file
88
middleware/api_token_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/sm-user-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/bouncer"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
)
|
||||
|
||||
// forbiddenBody is the 403 body of Scope and Permission.
|
||||
type forbiddenBody struct {
|
||||
Error bool `json:"error"`
|
||||
Message string `json:"message"`
|
||||
MissingScope string `json:"missing_scope"`
|
||||
MissingPermission string `json:"missing_permission"`
|
||||
}
|
||||
|
||||
// tokenWith is an in-memory credential holding scopes, built the way
|
||||
// MCPDispatch builds it.
|
||||
func tokenWith(scopes ...string) *models.APIToken {
|
||||
return &models.APIToken{Scopes: lagoon.Jsonable[[]string]{Data: scopes, Valid: true}}
|
||||
}
|
||||
|
||||
// serve runs mw over a handler answering 200, with ctx on the request.
|
||||
func serve(t *testing.T, mw func(http.Handler) http.Handler, ctx context.Context) (int, forbiddenBody) {
|
||||
t.Helper()
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
|
||||
mw(next).ServeHTTP(rec, req)
|
||||
var body forbiddenBody
|
||||
if rec.Code == http.StatusForbidden {
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("403 body %q: %v", rec.Body.String(), err)
|
||||
}
|
||||
}
|
||||
return rec.Code, body
|
||||
}
|
||||
|
||||
// TestForbiddenBodiesNameWhatIsMissing (D-15, D-20): a refusal by Scope or
|
||||
// Permission keeps error true and the message, and adds missing_scope or
|
||||
// missing_permission naming what is missing. No database.
|
||||
func TestForbiddenBodiesNameWhatIsMissing(t *testing.T) {
|
||||
scopeRefused := forbiddenBody{Error: true, Message: "Missing required scope: write", MissingScope: "write"}
|
||||
|
||||
t.Run("scope missing from the credential", func(t *testing.T) {
|
||||
ctx := bouncer.WithCredential(context.Background(), tokenWith("read"))
|
||||
code, body := serve(t, Scope("write"), ctx)
|
||||
if code != http.StatusForbidden || body != scopeRefused {
|
||||
t.Fatalf("Scope(write) with read = %d %+v, want 403 %+v", code, body, scopeRefused)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no credential", func(t *testing.T) {
|
||||
code, body := serve(t, Scope("write"), context.Background())
|
||||
if code != http.StatusForbidden || body != scopeRefused {
|
||||
t.Fatalf("Scope(write) without a credential = %d %+v, want 403 %+v", code, body, scopeRefused)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scope held", func(t *testing.T) {
|
||||
ctx := bouncer.WithCredential(context.Background(), tokenWith("read"))
|
||||
if code, _ := serve(t, Scope("read"), ctx); code != http.StatusOK {
|
||||
t.Fatalf("Scope(read) with read = %d, want 200", code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("permission missing", func(t *testing.T) {
|
||||
ctx := bouncer.WithUser(context.Background(), &bouncer.Principal{ID: 1, PermissionGrants: map[string]bool{"jz.bm.access_schedule": true}})
|
||||
want := forbiddenBody{Error: true, Message: "Forbidden.", MissingPermission: "jz.bm.access_courses"}
|
||||
code, body := serve(t, Permission("jz.bm.access_courses"), ctx)
|
||||
if code != http.StatusForbidden || body != want {
|
||||
t.Fatalf("Permission without the grant = %d %+v, want 403 %+v", code, body, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("permission granted", func(t *testing.T) {
|
||||
ctx := bouncer.WithUser(context.Background(), &bouncer.Principal{ID: 1, PermissionGrants: map[string]bool{"jz.bm.access_courses": true}})
|
||||
if code, _ := serve(t, Permission("jz.bm.access_courses"), ctx); code != http.StatusOK {
|
||||
t.Fatalf("Permission with the grant = %d, want 200", code)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user