mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 21:41:55 +00:00
240 lines
6.8 KiB
Go
240 lines
6.8 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"gitlab.com/mbugroup/lti-api.git/internal/apikeys"
|
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
sso "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/verifier"
|
|
userValidation "gitlab.com/mbugroup/lti-api.git/internal/modules/users/validations"
|
|
)
|
|
|
|
type stubUserService struct {
|
|
user *entity.User
|
|
err error
|
|
}
|
|
|
|
func (s *stubUserService) GetAll(_ *fiber.Ctx, _ *userValidation.Query) ([]entity.User, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (s *stubUserService) GetOne(_ *fiber.Ctx, _ uint) (*entity.User, error) {
|
|
return s.user, s.err
|
|
}
|
|
|
|
func (s *stubUserService) CreateOne(_ *fiber.Ctx, _ *userValidation.Create) (*entity.User, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *stubUserService) UpdateOne(_ *fiber.Ctx, _ *userValidation.Update, _ uint) (*entity.User, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *stubUserService) DeleteOne(_ *fiber.Ctx, _ uint) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *stubUserService) GetBySSOUserID(_ *fiber.Ctx, _ uint) (*entity.User, error) {
|
|
return s.user, s.err
|
|
}
|
|
|
|
type stubAPIKeyAuthenticator struct {
|
|
principal *apikeys.Principal
|
|
err error
|
|
}
|
|
|
|
func (s *stubAPIKeyAuthenticator) Authenticate(_ context.Context, _ string, _ string) (*apikeys.Principal, error) {
|
|
return s.principal, s.err
|
|
}
|
|
|
|
func TestAuthAllowsAPIKeyOnGet(t *testing.T) {
|
|
SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{
|
|
principal: &apikeys.Principal{
|
|
Name: "dashboard",
|
|
Permissions: []string{"perm.read"},
|
|
LocationIDs: []uint{3, 5},
|
|
},
|
|
})
|
|
defer SetAPIKeyAuthenticator(nil)
|
|
|
|
app := fiber.New()
|
|
app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error {
|
|
scope, err := ResolveLocationScope(c, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType,
|
|
"restrict": scope.Restrict,
|
|
"ids": scope.IDs,
|
|
})
|
|
})
|
|
|
|
req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
|
|
req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode != fiber.StatusOK {
|
|
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestAuthRejectsAPIKeyOnPost(t *testing.T) {
|
|
SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{
|
|
principal: &apikeys.Principal{
|
|
Name: "dashboard",
|
|
Permissions: []string{"perm.write"},
|
|
},
|
|
})
|
|
defer SetAPIKeyAuthenticator(nil)
|
|
|
|
app := fiber.New()
|
|
app.Post("/reports", Auth(&stubUserService{}), RequirePermissions("perm.write"), func(c *fiber.Ctx) error {
|
|
return c.SendStatus(fiber.StatusOK)
|
|
})
|
|
|
|
req := httptest.NewRequest(fiber.MethodPost, "/reports", nil)
|
|
req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode != fiber.StatusUnauthorized {
|
|
t.Fatalf("expected 401, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestAuthRejectsInvalidAPIKey(t *testing.T) {
|
|
SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInvalidAPIKey})
|
|
defer SetAPIKeyAuthenticator(nil)
|
|
|
|
app := fiber.New()
|
|
app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error {
|
|
return c.SendStatus(fiber.StatusOK)
|
|
})
|
|
|
|
req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
|
|
req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode != fiber.StatusUnauthorized {
|
|
t.Fatalf("expected 401, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestAuthRejectsInactiveAPIKey(t *testing.T) {
|
|
SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: apikeys.ErrInactiveKey})
|
|
defer SetAPIKeyAuthenticator(nil)
|
|
|
|
app := fiber.New()
|
|
app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error {
|
|
return c.SendStatus(fiber.StatusOK)
|
|
})
|
|
|
|
req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
|
|
req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode != fiber.StatusUnauthorized {
|
|
t.Fatalf("expected 401, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestAuthRejectsMissingPermission(t *testing.T) {
|
|
SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{
|
|
principal: &apikeys.Principal{
|
|
Name: "dashboard",
|
|
Permissions: []string{"perm.other"},
|
|
},
|
|
})
|
|
defer SetAPIKeyAuthenticator(nil)
|
|
|
|
app := fiber.New()
|
|
app.Get("/reports", Auth(&stubUserService{}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error {
|
|
return c.SendStatus(fiber.StatusOK)
|
|
})
|
|
|
|
req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
|
|
req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode != fiber.StatusForbidden {
|
|
t.Fatalf("expected 403, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestAuthAllowsBearerOnGet(t *testing.T) {
|
|
previousVerify := verifyAccessTokenFunc
|
|
previousProfile := fetchProfileFunc
|
|
defer func() {
|
|
verifyAccessTokenFunc = previousVerify
|
|
fetchProfileFunc = previousProfile
|
|
}()
|
|
|
|
verifyAccessTokenFunc = func(_ string) (*sso.VerificationResult, error) {
|
|
return &sso.VerificationResult{
|
|
UserID: 1,
|
|
Claims: &sso.AccessTokenClaims{
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
IssuedAt: jwt.NewNumericDate(time.Now().UTC()),
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
fetchProfileFunc = func(_ context.Context, _ string, _ *sso.VerificationResult) (*sso.UserProfile, error) {
|
|
return &sso.UserProfile{
|
|
Permissions: []sso.Permission{{Name: "perm.read"}},
|
|
LocationIDs: []uint{7},
|
|
}, nil
|
|
}
|
|
|
|
app := fiber.New()
|
|
app.Get("/reports", Auth(&stubUserService{user: &entity.User{Id: 9, Name: "API User"}}), RequirePermissions("perm.read"), func(c *fiber.Ctx) error {
|
|
return c.JSON(fiber.Map{"principal": c.Locals(authContextLocalsKey).(*AuthContext).PrincipalType})
|
|
})
|
|
|
|
req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
|
|
req.Header.Set("Authorization", "Bearer test-token")
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode != fiber.StatusOK {
|
|
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestAuthReturnsServerErrorWhenAPIKeyVerifierFailsUnexpectedly(t *testing.T) {
|
|
SetAPIKeyAuthenticator(&stubAPIKeyAuthenticator{err: errors.New("boom")})
|
|
defer SetAPIKeyAuthenticator(nil)
|
|
|
|
app := fiber.New()
|
|
app.Get("/reports", Auth(&stubUserService{}), func(c *fiber.Ctx) error {
|
|
return c.SendStatus(fiber.StatusOK)
|
|
})
|
|
|
|
req := httptest.NewRequest(fiber.MethodGet, "/reports", nil)
|
|
req.Header.Set("X-API-Key", "lti_dev_prefix_secret")
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode != fiber.StatusInternalServerError {
|
|
t.Fatalf("expected 500, got %d", resp.StatusCode)
|
|
}
|
|
}
|