mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:51:30 +00:00
auth-bundle-1 Phase 0: extract internal/auth/ from middleware package
Bundle 1 / Phase 0: pure refactor splitting auth surface out of internal/api/middleware so Bundle 2 (OIDC + sessions) and the broader RBAC primitive (roles, permissions, scoped grants) have a clean home. Moved to internal/auth/: NamedAPIKey, HashAPIKey, AuthConfig, NewAuthWithNamedKeys, NewAuth, UserKey, AdminKey, GetUser, IsAdmin. Added testfixtures.go (WithActor / WithAdmin / WithActorAdmin) so handler tests don't construct context manually. Stayed in internal/api/middleware/: RequestID, Logging, NewLogging, Recovery, RateLimitConfig, NewRateLimiter (now imports auth.GetUser for per-user keying per audit Category C), CORSConfig, NewCORS, ContentType, CORS, GetRequestID, responseWriter, Chain, audit middleware (now imports auth.GetUser). Updated 22 caller files across cmd/, internal/api/handler/, internal/api/middleware/, internal/mcp/. Existing m008_admin_gate_test.go now scans for auth.IsAdmin( substring; Phase 3 will further evolve to track auth.RequirePermission. Behavior unchanged: all handler / middleware / service / connector / cmd / mcp tests pass with no test-logic edits, only import-path renames. Phase 0 exit criteria: internal/auth/ exists with 6 files; middleware.go went 575 -> 422 lines (auth-related ~150 lines moved out); grep -rE 'middleware\.(GetUser|IsAdmin|UserKey|AdminKey|NamedAPIKey|HashAPIKey|NewAuth)' returns 0 hits; context.WithValue(.*middleware.UserKey/AdminKey) returns 0 hits; go vet ./... clean; go test -short ./... green across all packages tested. Branch: dev/auth-bundle-1. Per cowork/auth-bundle-1-prompt.md, do not merge to master without (1) make verify green, (2) >= 2 external testers confirm, (3) >= 90% coverage on internal/auth/ in .github/coverage-thresholds.yml.
This commit is contained in:
@@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
"github.com/certctl-io/certctl/internal/repository"
|
||||
)
|
||||
@@ -74,7 +74,7 @@ func (h AdminCRLCacheHandler) ListCache(w http.ResponseWriter, r *http.Request)
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
)
|
||||
|
||||
// fakeAdminCRLCacheService is the test stub for the
|
||||
@@ -67,7 +68,7 @@ func TestAdminCRLCache_AdminExplicitFalse_Returns403(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crl/cache", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -99,8 +100,8 @@ func TestAdminCRLCache_AdminPermitted_ForwardsActor(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crl/cache", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -131,7 +132,7 @@ func TestAdminCRLCache_RejectsNonGetMethod(t *testing.T) {
|
||||
h := NewAdminCRLCacheHandler(&fakeAdminCRLCacheService{})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crl/cache", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -150,7 +151,7 @@ func TestAdminCRLCache_PropagatesServiceError(t *testing.T) {
|
||||
h := NewAdminCRLCacheHandler(svc)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crl/cache", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/service"
|
||||
)
|
||||
|
||||
@@ -76,7 +76,7 @@ func (h AdminESTHandler) Profiles(w http.ResponseWriter, r *http.Request) {
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func (h AdminESTHandler) ReloadTrust(w http.ResponseWriter, r *http.Request) {
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/service"
|
||||
)
|
||||
|
||||
@@ -65,7 +66,7 @@ func TestAdminEST_Profiles_AdminExplicitFalse_Returns403(t *testing.T) {
|
||||
h := NewAdminESTHandler(svc)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/est/profiles", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.Profiles(w, req)
|
||||
@@ -86,7 +87,7 @@ func TestAdminEST_Profiles_AdminTrue_Returns200(t *testing.T) {
|
||||
h := NewAdminESTHandler(svc)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/est/profiles", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.Profiles(w, req)
|
||||
@@ -121,7 +122,7 @@ func TestAdminEST_Profiles_NilRowsSerializedAsEmptyArray(t *testing.T) {
|
||||
h := NewAdminESTHandler(svc)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/est/profiles", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.Profiles(w, req)
|
||||
@@ -157,7 +158,7 @@ func TestAdminEST_ReloadTrust_AdminExplicitFalse_Returns403(t *testing.T) {
|
||||
strings.NewReader(`{"path_id":"corp"}`))
|
||||
req.ContentLength = int64(len(`{"path_id":"corp"}`))
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -177,7 +178,7 @@ func TestAdminEST_ReloadTrust_HappyPath(t *testing.T) {
|
||||
strings.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -197,7 +198,7 @@ func TestAdminEST_ReloadTrust_UnknownPathID_Returns404(t *testing.T) {
|
||||
strings.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -214,7 +215,7 @@ func TestAdminEST_ReloadTrust_MTLSDisabled_Returns409(t *testing.T) {
|
||||
strings.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -231,7 +232,7 @@ func TestAdminEST_ReloadTrust_ParseError_Returns500(t *testing.T) {
|
||||
strings.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -248,7 +249,7 @@ func TestAdminEST_ReloadTrust_MalformedJSON_Returns400(t *testing.T) {
|
||||
strings.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/service"
|
||||
)
|
||||
|
||||
@@ -90,7 +90,7 @@ func (h AdminSCEPIntuneHandler) Profiles(w http.ResponseWriter, r *http.Request)
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (h AdminSCEPIntuneHandler) Stats(w http.ResponseWriter, r *http.Request) {
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
@@ -146,7 +146,7 @@ func (h AdminSCEPIntuneHandler) ReloadTrust(w http.ResponseWriter, r *http.Reque
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/service"
|
||||
)
|
||||
|
||||
@@ -81,7 +82,7 @@ func TestAdminSCEPIntune_AdminExplicitFalse_Returns403(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/intune/stats", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -106,8 +107,8 @@ func TestAdminSCEPIntune_AdminPermitted_ForwardsActor(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/intune/stats", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -160,7 +161,7 @@ func TestAdminSCEPIntuneReload_AdminExplicitFalse_Returns403(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
|
||||
strings.NewReader(`{"path_id":"corp"}`))
|
||||
req.ContentLength = int64(len(`{"path_id":"corp"}`))
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, false)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, false)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -181,8 +182,8 @@ func TestAdminSCEPIntuneReload_AdminPermitted_ForwardsActor(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
|
||||
strings.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -211,7 +212,7 @@ func TestAdminSCEPIntuneReload_AdminPermitted_ForwardsActor(t *testing.T) {
|
||||
func TestAdminSCEPIntuneStats_RejectsNonGetMethod(t *testing.T) {
|
||||
h := NewAdminSCEPIntuneHandler(&fakeAdminSCEPIntuneService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/stats", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.Stats(w, req)
|
||||
@@ -223,7 +224,7 @@ func TestAdminSCEPIntuneStats_RejectsNonGetMethod(t *testing.T) {
|
||||
func TestAdminSCEPIntuneReload_RejectsNonPostMethod(t *testing.T) {
|
||||
h := NewAdminSCEPIntuneHandler(&fakeAdminSCEPIntuneService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/intune/reload-trust", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -236,7 +237,7 @@ func TestAdminSCEPIntuneStats_PropagatesServiceError(t *testing.T) {
|
||||
svc := &fakeAdminSCEPIntuneService{statsErr: errors.New("registry walk failed")}
|
||||
h := NewAdminSCEPIntuneHandler(svc)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/intune/stats", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.Stats(w, req)
|
||||
@@ -251,7 +252,7 @@ func TestAdminSCEPIntuneReload_ProfileNotFound_Returns404(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
|
||||
strings.NewReader(`{"path_id":"nonexistent"}`))
|
||||
req.ContentLength = int64(len(`{"path_id":"nonexistent"}`))
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -266,7 +267,7 @@ func TestAdminSCEPIntuneReload_IntuneDisabled_Returns409(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
|
||||
strings.NewReader(`{"path_id":"iot"}`))
|
||||
req.ContentLength = int64(len(`{"path_id":"iot"}`))
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -281,7 +282,7 @@ func TestAdminSCEPIntuneReload_BadReloadPropagates500(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
|
||||
strings.NewReader(`{"path_id":"corp"}`))
|
||||
req.ContentLength = int64(len(`{"path_id":"corp"}`))
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -294,7 +295,7 @@ func TestAdminSCEPIntuneReload_EmptyBodyTargetsLegacyRoot(t *testing.T) {
|
||||
svc := &fakeAdminSCEPIntuneService{}
|
||||
h := NewAdminSCEPIntuneHandler(svc)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -312,7 +313,7 @@ func TestAdminSCEPIntuneReload_RejectsMalformedJSON(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/intune/reload-trust",
|
||||
strings.NewReader(bad))
|
||||
req.ContentLength = int64(len(bad))
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.ReloadTrust(w, req)
|
||||
@@ -379,7 +380,7 @@ func TestAdminSCEPProfiles_AdminExplicitFalse_Returns403(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/profiles", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -417,8 +418,8 @@ func TestAdminSCEPProfiles_AdminPermitted_ForwardsActor(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/profiles", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -461,7 +462,7 @@ func TestAdminSCEPProfiles_AdminPermitted_ForwardsActor(t *testing.T) {
|
||||
func TestAdminSCEPProfiles_RejectsNonGetMethod(t *testing.T) {
|
||||
h := NewAdminSCEPIntuneHandler(&fakeAdminSCEPIntuneService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/scep/profiles", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.Profiles(w, req)
|
||||
@@ -474,7 +475,7 @@ func TestAdminSCEPProfiles_PropagatesServiceError(t *testing.T) {
|
||||
svc := &fakeAdminSCEPIntuneService{profilesErr: errors.New("registry walk failed")}
|
||||
h := NewAdminSCEPIntuneHandler(svc)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/scep/profiles", nil)
|
||||
ctx := context.WithValue(context.Background(), middleware.AdminKey{}, true)
|
||||
ctx := context.WithValue(context.Background(), auth.AdminKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.Profiles(w, req)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
"github.com/certctl-io/certctl/internal/repository"
|
||||
"github.com/certctl-io/certctl/internal/service"
|
||||
@@ -111,7 +112,7 @@ func (h ApprovalHandler) GetApproval(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Approve transitions a pending approval request to approved + transitions
|
||||
// the linked Job from AwaitingApproval to Pending. RBAC: the authenticated
|
||||
// actor extracted via middleware.UserKey must NOT equal the request's
|
||||
// actor extracted via auth.UserKey must NOT equal the request's
|
||||
// RequestedBy — the service-layer check enforces this and the handler
|
||||
// surfaces it as HTTP 403.
|
||||
//
|
||||
@@ -153,7 +154,7 @@ func (h ApprovalHandler) decision(w http.ResponseWriter, r *http.Request, action
|
||||
// Extract authenticated actor. The auth middleware sets UserKey to the
|
||||
// API-key NamedAPIKey.Name (or empty for unauthenticated). RBAC at the
|
||||
// service layer requires a non-empty actor.
|
||||
actor, _ := r.Context().Value(middleware.UserKey{}).(string)
|
||||
actor, _ := r.Context().Value(auth.UserKey{}).(string)
|
||||
if actor == "" {
|
||||
ErrorWithRequestID(w, http.StatusUnauthorized,
|
||||
"authentication required to approve / reject", requestID)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
"github.com/certctl-io/certctl/internal/repository"
|
||||
"github.com/certctl-io/certctl/internal/service"
|
||||
@@ -117,7 +117,7 @@ func reqWithActor(t *testing.T, method, target string, body string, actor string
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if actor != "" {
|
||||
req = req.WithContext(context.WithValue(req.Context(), middleware.UserKey{}, actor))
|
||||
req = req.WithContext(context.WithValue(req.Context(), auth.UserKey{}, actor))
|
||||
}
|
||||
if pathID != "" {
|
||||
req.SetPathValue("id", pathID)
|
||||
|
||||
@@ -172,7 +172,7 @@ func authenticatedContext(actor string) context.Context {
|
||||
type userKey struct{}
|
||||
// The middleware UserKey is a private type in the middleware package, so
|
||||
// in this handler test we can't construct one directly. Bulk-renew and
|
||||
// bulk-reassign read the actor through the same middleware.GetUser path
|
||||
// bulk-reassign read the actor through the same auth.GetUser path
|
||||
// that bulk-revoke does — adminContext() in the existing test suite is
|
||||
// the canonical helper. Reuse it (delivers both UserKey and AdminKey).
|
||||
_ = userKey{}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ func (m *mockBulkRenewalService) BulkRenew(ctx context.Context, criteria domain.
|
||||
// bulk-renew is NOT admin-gated, any authenticated caller can use it.
|
||||
func authedContext() context.Context {
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id-renew")
|
||||
ctx = context.WithValue(ctx, middleware.UserKey{}, "alice")
|
||||
ctx = context.WithValue(ctx, auth.UserKey{}, "alice")
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -126,7 +127,7 @@ func TestBulkRenew_Handler_ActorAttribution(t *testing.T) {
|
||||
h.BulkRenew(w, req)
|
||||
|
||||
if capturedActor != "alice" {
|
||||
t.Errorf("actor not threaded from middleware.UserKey: got %q, want 'alice'", capturedActor)
|
||||
t.Errorf("actor not threaded from auth.UserKey: got %q, want 'alice'", capturedActor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
)
|
||||
|
||||
@@ -53,7 +54,7 @@ func (h BulkRevocationHandler) BulkRevoke(w http.ResponseWriter, r *http.Request
|
||||
// M-003: admin-only gate. Non-admin callers are rejected before any
|
||||
// criteria/body processing to avoid leaking validation behavior to
|
||||
// unauthorized actors.
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
ErrorWithRequestID(w, http.StatusForbidden,
|
||||
"Bulk revocation requires admin privileges",
|
||||
requestID)
|
||||
@@ -127,7 +128,7 @@ func (h BulkRevocationHandler) BulkRevokeEST(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
requestID := middleware.GetRequestID(r.Context())
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
ErrorWithRequestID(w, http.StatusForbidden,
|
||||
"EST bulk revocation requires admin privileges", requestID)
|
||||
return
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
)
|
||||
|
||||
@@ -31,7 +32,7 @@ func (m *mockBulkRevocationService) BulkRevoke(ctx context.Context, criteria dom
|
||||
// M-003: bulk revocation handler requires admin context to reach the service.
|
||||
func adminContext() context.Context {
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id-bulk")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -243,7 +244,7 @@ func TestBulkRevoke_AdminExplicitFalse_Returns403(t *testing.T) {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -273,8 +274,8 @@ func TestBulkRevoke_AdminPermitted_ForwardsActor(t *testing.T) {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
ctx := context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
)
|
||||
|
||||
// HealthHandler handles health and readiness check endpoints.
|
||||
@@ -152,8 +152,8 @@ func (h HealthHandler) AuthInfo(w http.ResponseWriter, r *http.Request) {
|
||||
func (h HealthHandler) AuthCheck(w http.ResponseWriter, r *http.Request) {
|
||||
response := map[string]interface{}{
|
||||
"status": "authenticated",
|
||||
"user": middleware.GetUser(r.Context()),
|
||||
"admin": middleware.IsAdmin(r.Context()),
|
||||
"user": auth.GetUser(r.Context()),
|
||||
"admin": auth.IsAdmin(r.Context()),
|
||||
}
|
||||
JSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
_ "github.com/lib/pq" // Bundle-5 / H-006: postgres driver for /ready DB-probe regression test
|
||||
)
|
||||
|
||||
@@ -238,8 +238,8 @@ func TestAuthCheck_AdminCaller_ReportsAdminTrue(t *testing.T) {
|
||||
handler := NewHealthHandler("api-key", nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/check", nil)
|
||||
ctx := context.WithValue(req.Context(), middleware.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, middleware.UserKey{}, "ops-admin")
|
||||
ctx := context.WithValue(req.Context(), auth.AdminKey{}, true)
|
||||
ctx = context.WithValue(ctx, auth.UserKey{}, "ops-admin")
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -276,8 +276,8 @@ func TestAuthCheck_NonAdminCaller_ReportsAdminFalse(t *testing.T) {
|
||||
handler := NewHealthHandler("api-key", nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/check", nil)
|
||||
ctx := context.WithValue(req.Context(), middleware.AdminKey{}, false)
|
||||
ctx = context.WithValue(ctx, middleware.UserKey{}, "alice")
|
||||
ctx := context.WithValue(req.Context(), auth.AdminKey{}, false)
|
||||
ctx = context.WithValue(ctx, auth.UserKey{}, "alice")
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/crypto/signer"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
"github.com/certctl-io/certctl/internal/service"
|
||||
@@ -36,7 +37,7 @@ type IntermediateCAServicer interface {
|
||||
// All routes are pinned at /api/v1/issuers/{id}/intermediates and
|
||||
// /api/v1/intermediates/{id}.
|
||||
//
|
||||
// Admin gate: every method calls middleware.IsAdmin first and surfaces
|
||||
// Admin gate: every method calls auth.IsAdmin first and surfaces
|
||||
// HTTP 403 for non-admin Bearer callers (M-003 admin-gating pattern,
|
||||
// matches AdminCRLCacheHandler / AdminESTHandler / AdminSCEPIntuneHandler).
|
||||
// CA hierarchy management is a high-blast-radius surface — adding a
|
||||
@@ -111,7 +112,7 @@ func (h IntermediateCAHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
@@ -122,7 +123,7 @@ func (h IntermediateCAHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
ErrorWithRequestID(w, http.StatusBadRequest, "issuer id required", requestID)
|
||||
return
|
||||
}
|
||||
actor, _ := r.Context().Value(middleware.UserKey{}).(string)
|
||||
actor, _ := r.Context().Value(auth.UserKey{}).(string)
|
||||
if actor == "" {
|
||||
ErrorWithRequestID(w, http.StatusUnauthorized,
|
||||
"authentication required", requestID)
|
||||
@@ -211,7 +212,7 @@ func (h IntermediateCAHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
@@ -237,7 +238,7 @@ func (h IntermediateCAHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
@@ -270,7 +271,7 @@ func (h IntermediateCAHandler) Retire(w http.ResponseWriter, r *http.Request) {
|
||||
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
if !middleware.IsAdmin(r.Context()) {
|
||||
if !auth.IsAdmin(r.Context()) {
|
||||
Error(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
@@ -281,7 +282,7 @@ func (h IntermediateCAHandler) Retire(w http.ResponseWriter, r *http.Request) {
|
||||
ErrorWithRequestID(w, http.StatusBadRequest, "id required", requestID)
|
||||
return
|
||||
}
|
||||
actor, _ := r.Context().Value(middleware.UserKey{}).(string)
|
||||
actor, _ := r.Context().Value(auth.UserKey{}).(string)
|
||||
if actor == "" {
|
||||
ErrorWithRequestID(w, http.StatusUnauthorized,
|
||||
"authentication required", requestID)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
"github.com/certctl-io/certctl/internal/service"
|
||||
)
|
||||
@@ -80,8 +80,8 @@ func (m *mockIntermediateCAService) LoadHierarchy(ctx context.Context, issuerID
|
||||
// authenticated user — the standard "admin caller" shape for these
|
||||
// tests.
|
||||
func withAdmin(actor string, admin bool) context.Context {
|
||||
ctx := context.WithValue(context.Background(), middleware.UserKey{}, actor)
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, admin)
|
||||
ctx := context.WithValue(context.Background(), auth.UserKey{}, actor)
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, admin)
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -177,8 +177,8 @@ func TestIntermediateCA_Handler_AdminExplicitFalse_Returns403(t *testing.T) {
|
||||
bytes.NewReader([]byte(`{"name":"r"}`)))
|
||||
req.SetPathValue("id", "iss-1")
|
||||
// AdminKey explicitly set to false — distinct from missing key.
|
||||
ctx := context.WithValue(context.Background(), middleware.UserKey{}, "alice")
|
||||
ctx = context.WithValue(ctx, middleware.AdminKey{}, false)
|
||||
ctx := context.WithValue(context.Background(), auth.UserKey{}, "alice")
|
||||
ctx = context.WithValue(ctx, auth.AdminKey{}, false)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
h.Create(w, req)
|
||||
|
||||
@@ -14,19 +14,19 @@ import (
|
||||
//
|
||||
// The audit's request is "Admin-gated operation role-gate test coverage
|
||||
// needs verification". Verified-already-clean recon: only one handler
|
||||
// in internal/api/handler/ calls middleware.IsAdmin to gate access:
|
||||
// in internal/api/handler/ calls auth.IsAdmin to gate access:
|
||||
// bulk_revocation.go — which has 3 dedicated tests
|
||||
// (NonAdmin_Returns403, AdminExplicitFalse_Returns403,
|
||||
// AdminPermitted_ForwardsActor) covering all three branches.
|
||||
//
|
||||
// This test enforces the invariant going forward by walking every
|
||||
// .go file in this package, finding every middleware.IsAdmin call
|
||||
// .go file in this package, finding every auth.IsAdmin call
|
||||
// site, and asserting the file appears in AdminGatedHandlers below.
|
||||
// Adding a new middleware.IsAdmin call without updating the constant
|
||||
// Adding a new auth.IsAdmin call without updating the constant
|
||||
// AND adding a parallel test triplet fails CI.
|
||||
|
||||
// AdminGatedHandlers is the documented allowlist of handler files that
|
||||
// gate access on middleware.IsAdmin. Every entry MUST have:
|
||||
// gate access on auth.IsAdmin. Every entry MUST have:
|
||||
// - a non-admin-rejection test ("_NonAdmin_Returns403")
|
||||
// - an explicit-false-admin-rejection test ("_AdminExplicitFalse_Returns403")
|
||||
// - an admin-allowed actor-attribution test ("_AdminPermitted_ForwardsActor")
|
||||
@@ -43,7 +43,7 @@ var AdminGatedHandlers = map[string]string{
|
||||
}
|
||||
|
||||
// InformationalIsAdminCallers is the documented allowlist of files that
|
||||
// call middleware.IsAdmin without using the result to gate access. The
|
||||
// call auth.IsAdmin without using the result to gate access. The
|
||||
// only legitimate use of an informational call is reporting the flag to
|
||||
// a downstream consumer (e.g. health.go::AuthCheck reports admin to the
|
||||
// GUI so it can hide admin-only buttons).
|
||||
@@ -64,7 +64,7 @@ func TestM008_AdminGatedHandlers_PinExpectedSet(t *testing.T) {
|
||||
|
||||
if !slicesEqual008(actual, expected) {
|
||||
t.Errorf(
|
||||
"middleware.IsAdmin call sites changed:\n"+
|
||||
"auth.IsAdmin call sites changed:\n"+
|
||||
" actual: %v\n"+
|
||||
" expected: %v\n"+
|
||||
"\n"+
|
||||
@@ -143,10 +143,10 @@ func scanIsAdminCallers(dir string) ([]string, error) {
|
||||
if parseErr != nil {
|
||||
continue
|
||||
}
|
||||
// Substring-match middleware.IsAdmin — cheap and sufficient
|
||||
// Substring-match auth.IsAdmin — cheap and sufficient
|
||||
// because the import path is fixed and there's no aliasing
|
||||
// shenanigans elsewhere in this package.
|
||||
if strings.Contains(string(body), "middleware.IsAdmin(") {
|
||||
if strings.Contains(string(body), "auth.IsAdmin(") {
|
||||
out = append(out, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/certctl-io/certctl/internal/api/middleware"
|
||||
"github.com/certctl-io/certctl/internal/auth"
|
||||
)
|
||||
|
||||
// resolveActor extracts the authenticated named-key identity from the request
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
// or "api" — always go through this helper so the named-key identity flows to
|
||||
// services and the audit trail.
|
||||
func resolveActor(ctx context.Context) string {
|
||||
if user := middleware.GetUser(ctx); user != "" {
|
||||
if user := auth.GetUser(ctx); user != "" {
|
||||
return user
|
||||
}
|
||||
return "api"
|
||||
|
||||
Reference in New Issue
Block a user