From 9e1edd139b6e8203d8d334e3485027031fa8a8ae Mon Sep 17 00:00:00 2001 From: maansaake Date: Sun, 23 Aug 2026 10:29:04 +0200 Subject: [PATCH 1/3] test: align integration/security test file organisation and naming Reorganise the integration, security, and connector suites so each API resource group maps to a single test file, merge sub-resources into their parent resource files, and enforce a consistent Test naming convention. Admin API: - add admin_api_auth_test.go (superuser + admin-user login/logout/refresh/ password/me), dissolving admin_api_test.go and admin_api_session_test.go - merge group bindings into admin_api_users_test.go - split flow and oas into admin_api_flow_test.go / admin_api_oas_test.go - move shared allPermissionIDs var into state.go - prefix debug/permissions tests (TestAdminDebug*, TestAdminPermissions*) Basic Auth API: - merge refresh into organisations, bindings into users - rename auth_basic_api_test.go -> auth_basic_api_access_control_test.go - prefix all tests with BasicAuth (TestBasicAuth*) Other: - rename auth_basic_test.go -> gateway_auth_basic_test.go (flow component) - de-snake-case cookies/cors test names across integration and security - remove empty security/csrf_test.go No test cases were deleted: a signature analysis confirmed the apparent overlaps are legitimate parallel coverage of two distinct APIs or RBAC vs plain-resource tests. Test function counts are unchanged (147 integration, 10 security). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../suites/integration/admin_api_auth_test.go | 351 ++++++++++++++++++ .../integration/admin_api_debug_test.go | 68 ++-- .../suites/integration/admin_api_flow_test.go | 114 ++++++ .../admin_api_group_bindings_test.go | 232 ------------ test/suites/integration/admin_api_oas_test.go | 81 ++++ .../integration/admin_api_permissions_test.go | 88 ++--- .../integration/admin_api_session_test.go | 125 ------- test/suites/integration/admin_api_test.go | 230 ------------ .../integration/admin_api_users_test.go | 247 ++++++------ ... => auth_basic_api_access_control_test.go} | 65 +--- .../auth_basic_api_bindings_test.go | 268 ------------- .../integration/auth_basic_api_groups_test.go | 52 +-- .../auth_basic_api_organisations_test.go | 117 ++++-- .../integration/auth_basic_api_users_test.go | 315 ++++++++++++++-- test/suites/integration/cookies_test.go | 4 +- test/suites/integration/cors_test.go | 6 +- ...sic_test.go => gateway_auth_basic_test.go} | 8 +- test/suites/integration/state.go | 4 + test/suites/security/cookies_test.go | 4 +- test/suites/security/cors_test.go | 6 +- test/suites/security/csrf_test.go | 1 - test/suites/security/tls_test.go | 8 +- 22 files changed, 1192 insertions(+), 1202 deletions(-) create mode 100644 test/suites/integration/admin_api_auth_test.go create mode 100644 test/suites/integration/admin_api_flow_test.go delete mode 100644 test/suites/integration/admin_api_group_bindings_test.go create mode 100644 test/suites/integration/admin_api_oas_test.go delete mode 100644 test/suites/integration/admin_api_session_test.go delete mode 100644 test/suites/integration/admin_api_test.go rename test/suites/integration/{auth_basic_api_test.go => auth_basic_api_access_control_test.go} (87%) delete mode 100644 test/suites/integration/auth_basic_api_bindings_test.go rename test/suites/integration/{auth_basic_test.go => gateway_auth_basic_test.go} (94%) delete mode 100644 test/suites/security/csrf_test.go diff --git a/test/suites/integration/admin_api_auth_test.go b/test/suites/integration/admin_api_auth_test.go new file mode 100644 index 0000000..5309b83 --- /dev/null +++ b/test/suites/integration/admin_api_auth_test.go @@ -0,0 +1,351 @@ +package integration + +import ( + lib "github.com/trebent/kerberos/test/lib" + "net/http" + "testing" + + adminapi "github.com/trebent/kerberos/test/client/admin" +) + +func TestAdminLoginSuperuser(t *testing.T) { + superRequestEditor := lib.SuperLogin(t) + + t.Log("Logging the superuser out") + superLogoutResp, err := lib.AdminClient.LogoutSuperuserWithResponse( + t.Context(), + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(superLogoutResp.StatusCode(), http.StatusNoContent, t) + + t.Log("Running a GET flow request with the old session to verify it is invalidated") + // Verify the old session is truly invalidated by attempting to access a protected endpoint with it. + getFlowResp, err := lib.AdminClient.GetFlowWithResponse( + t.Context(), + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getFlowResp.StatusCode(), http.StatusUnauthorized, t) +} + +func TestAdminLoginSuperuserFailure(t *testing.T) { + t.Parallel() + superLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse( + t.Context(), + adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: "not-correct"}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(superLoginResp.StatusCode(), http.StatusUnauthorized, t) + lib.VerifyAdminAPIErrorResponse(superLoginResp.JSON401, t) +} + +func TestAdminSuperuserLoginOASValidation(t *testing.T) { + t.Parallel() + badSuperLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse(t.Context(), adminapi.LoginSuperuserJSONRequestBody{}) + lib.CheckErr(err, t) + lib.VerifyStatusCode(badSuperLoginResp.StatusCode(), http.StatusBadRequest, t) + lib.VerifyAdminAPIErrorResponse(badSuperLoginResp.JSON400, t) +} + +// TestAdminRefreshSuperuserSessionNoRefreshCookie verifies that calling the superuser refresh +// endpoint without a refresh cookie returns 401. A missing session cookie alone is not enough +// to trigger an error — only the missing refresh cookie matters here. +func TestAdminRefreshSuperuserSessionNoRefreshCookie(t *testing.T) { + t.Parallel() + resp, err := lib.AdminClient.RefreshSuperuserSessionWithResponse(t.Context()) + lib.CheckErr(err, t) + lib.VerifyStatusCode(resp.StatusCode(), http.StatusUnauthorized, t) + lib.VerifyAdminAPIErrorResponse(resp.JSON401, t) +} + +// TestAdminRefreshSuperuserSession verifies that the superuser refresh endpoint issues a new +// session when called with only the refresh cookie (no session cookie required). +func TestAdminRefreshSuperuserSession(t *testing.T) { + t.Parallel() + loginResp, err := lib.AdminClient.LoginSuperuserWithResponse( + t.Context(), + adminapi.LoginSuperuserJSONRequestBody{ + ClientId: lib.SuperUserClientID, + ClientSecret: lib.SuperUserClientSecret, + }, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) + + // Use only the refresh cookie — deliberately omit the session cookie to prove it is not required. + refreshEditor := lib.RefreshCookieRequestEditor(loginResp.HTTPResponse, t) + + refreshResp, err := lib.AdminClient.RefreshSuperuserSessionWithResponse( + t.Context(), + adminapi.RequestEditorFn(refreshEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(refreshResp.StatusCode(), http.StatusNoContent, t) +} + +// TestAdminRefreshSuperuserSessionForbidden verifies that a non-superuser admin refresh token +// is rejected by the superuser refresh endpoint with 403. +func TestAdminRefreshSuperuserSessionForbidden(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + name := lib.Username() + const pass = "password123" + createResp, err := lib.AdminClient.CreateUserWithResponse( + t.Context(), + adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + + // Login as the regular admin user to get a non-superuser refresh token. + loginResp, err := lib.AdminClient.LoginWithResponse( + t.Context(), + adminapi.LoginJSONRequestBody{Username: name, Password: pass}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) + + // Use only the refresh cookie from the regular user session. + userRefreshEditor := lib.RefreshCookieRequestEditor(loginResp.HTTPResponse, t) + + refreshResp, err := lib.AdminClient.RefreshSuperuserSessionWithResponse( + t.Context(), + adminapi.RequestEditorFn(userRefreshEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(refreshResp.StatusCode(), http.StatusForbidden, t) + lib.VerifyAdminAPIErrorResponse(refreshResp.JSON403, t) +} + +// TestAdminRefreshUserSessionNoRefreshCookie verifies that calling the admin user refresh +// endpoint without a refresh cookie returns 401. +func TestAdminRefreshUserSessionNoRefreshCookie(t *testing.T) { + t.Parallel() + resp, err := lib.AdminClient.RefreshUserSessionWithResponse(t.Context()) + lib.CheckErr(err, t) + lib.VerifyStatusCode(resp.StatusCode(), http.StatusUnauthorized, t) + lib.VerifyAdminAPIErrorResponse(resp.JSON401, t) +} + +// TestAdminRefreshUserSession verifies that the admin user refresh endpoint issues a new +// session when called with only the refresh cookie (no session cookie required). +func TestAdminRefreshUserSession(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + name := lib.Username() + const pass = "password123" + createResp, err := lib.AdminClient.CreateUserWithResponse( + t.Context(), + adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + + loginResp, err := lib.AdminClient.LoginWithResponse( + t.Context(), + adminapi.LoginJSONRequestBody{Username: name, Password: pass}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) + + // Use only the refresh cookie — deliberately omit the session cookie. + refreshEditor := lib.RefreshCookieRequestEditor(loginResp.HTTPResponse, t) + + refreshResp, err := lib.AdminClient.RefreshUserSessionWithResponse( + t.Context(), + adminapi.RequestEditorFn(refreshEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(refreshResp.StatusCode(), http.StatusNoContent, t) +} + +// TestAdminUserLoginLogout verifies that an admin user can log in, access protected endpoints, +// log out, and that their session is invalidated afterwards. +func TestAdminUserLoginLogout(t *testing.T) { + superRequestEditor := lib.SuperLogin(t) + + name := lib.Username() + const pass = "loginpassword123" + createResp, err := lib.AdminClient.CreateUserWithResponse( + t.Context(), + adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + + adminRequestEditor := lib.AdminUserLogin(t, name, pass) + + // GetPermissions is accessible to any authenticated admin user (no specific permission required). + getPermsResp, err := lib.AdminClient.GetPermissionsWithResponse( + t.Context(), + adminapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getPermsResp.StatusCode(), http.StatusOK, t) + + logoutResp, err := lib.AdminClient.LogoutWithResponse( + t.Context(), + adminapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(logoutResp.StatusCode(), http.StatusNoContent, t) + + getPermsResp, err = lib.AdminClient.GetPermissionsWithResponse( + t.Context(), + adminapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getPermsResp.StatusCode(), http.StatusUnauthorized, t) +} + +// TestAdminUserLoginFailure verifies that login with incorrect credentials returns 401. +func TestAdminUserLoginFailure(t *testing.T) { + t.Parallel() + loginResp, err := lib.AdminClient.LoginWithResponse( + t.Context(), + adminapi.LoginJSONRequestBody{Username: "no-such-user", Password: "wrong"}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusUnauthorized, t) + lib.VerifyAdminAPIErrorResponse(loginResp.JSON401, t) +} + +// TestAdminUserChangePassword verifies that an admin user can change their password, +// that the old credentials are rejected, and that the new credentials work. +func TestAdminUserChangePassword(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + name := lib.Username() + const oldPass = "oldpassword123" + const newPass = "newpassword456" + + createResp, err := lib.AdminClient.CreateUserWithResponse( + t.Context(), + adminapi.CreateUserJSONRequestBody{Username: name, Password: oldPass}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + + adminRequestEditor := lib.AdminUserLogin(t, name, oldPass) + + changeResp, err := lib.AdminClient.ChangeUserPasswordWithResponse( + t.Context(), + createResp.JSON201.Id, + adminapi.ChangeUserPasswordJSONRequestBody{OldPassword: oldPass, NewPassword: newPass}, + adminapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(changeResp.StatusCode(), http.StatusNoContent, t) + + oldLoginResp, err := lib.AdminClient.LoginWithResponse( + t.Context(), + adminapi.LoginJSONRequestBody{Username: name, Password: oldPass}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(oldLoginResp.StatusCode(), http.StatusUnauthorized, t) + + _ = lib.AdminUserLogin(t, name, newPass) +} + +// TestAdminUserChangePasswordWrongOld verifies that providing the wrong old password is rejected. +func TestAdminUserChangePasswordWrongOld(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + name := lib.Username() + const pass = "correctpassword123" + + createResp, err := lib.AdminClient.CreateUserWithResponse( + t.Context(), + adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + + changeResp, err := lib.AdminClient.ChangeUserPasswordWithResponse( + t.Context(), + createResp.JSON201.Id, + adminapi.ChangeUserPasswordJSONRequestBody{OldPassword: "wrong-old-pass", NewPassword: "newpass"}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(changeResp.StatusCode(), http.StatusBadRequest, t) + lib.VerifyAdminAPIErrorResponse(changeResp.JSON400, t) +} + +// TestAdminMeNormalUser verifies that a normal admin user receives their own user info from /me. +func TestAdminMeNormalUser(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + name := lib.Username() + const pass = "mepassword123" + createResp, err := lib.AdminClient.CreateUserWithResponse( + t.Context(), + adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + + userRequestEditor := lib.AdminUserLogin(t, name, pass) + + meResp, err := lib.AdminClient.GetMeWithResponse( + t.Context(), + adminapi.RequestEditorFn(userRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(meResp.StatusCode(), http.StatusOK, t) + + if meResp.JSON200 == nil { + t.Fatal("Expected non-nil JSON200 body") + } + if meResp.JSON200.IsSuperuser { + t.Fatal("Expected isSuperuser=false for normal admin user") + } + if meResp.JSON200.User == nil { + t.Fatal("Expected non-nil user field for normal admin user") + } + lib.Matches(meResp.JSON200.User.Username, name, t) + lib.Matches(meResp.JSON200.User.Id, createResp.JSON201.Id, t) +} + +// TestAdminMeSuperuser verifies that the superuser receives isSuperuser=true and no user field from /me. +func TestAdminMeSuperuser(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + meResp, err := lib.AdminClient.GetMeWithResponse( + t.Context(), + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(meResp.StatusCode(), http.StatusOK, t) + + if meResp.JSON200 == nil { + t.Fatal("Expected non-nil JSON200 body") + } + if !meResp.JSON200.IsSuperuser { + t.Fatal("Expected isSuperuser=true for superuser") + } + if meResp.JSON200.User != nil { + t.Fatal("Expected nil user field for superuser") + } +} + +// TestAdminMeUnauthenticated verifies that calling /me without a session returns 401. +func TestAdminMeUnauthenticated(t *testing.T) { + t.Parallel() + meResp, err := lib.AdminClient.GetMeWithResponse(t.Context()) + lib.CheckErr(err, t) + lib.VerifyStatusCode(meResp.StatusCode(), http.StatusUnauthorized, t) +} diff --git a/test/suites/integration/admin_api_debug_test.go b/test/suites/integration/admin_api_debug_test.go index b65c99a..3d08180 100644 --- a/test/suites/integration/admin_api_debug_test.go +++ b/test/suites/integration/admin_api_debug_test.go @@ -10,9 +10,9 @@ import ( // --- StartDebugSession --- -// TestDebugStartSession verifies that a superuser can start a debug session and +// TestAdminDebugStartSession verifies that a superuser can start a debug session and // the response body contains the correct fields. -func TestDebugStartSession(t *testing.T) { +func TestAdminDebugStartSession(t *testing.T) { superRequestEditor := lib.SuperLogin(t) resp, err := lib.AdminClient.StartDebugSessionWithResponse( @@ -55,9 +55,9 @@ func TestDebugStartSession(t *testing.T) { lib.VerifyStatusCode(deleteResp.StatusCode(), http.StatusNoContent, t) } -// TestDebugStartSessionConflict verifies that starting a second debug session for a +// TestAdminDebugStartSessionConflict verifies that starting a second debug session for a // backend that already has an active session returns 409 conflict. -func TestDebugStartSessionConflict(t *testing.T) { +func TestAdminDebugStartSessionConflict(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -90,9 +90,9 @@ func TestDebugStartSessionConflict(t *testing.T) { // --- ListDebugSessions --- -// TestDebugListSessionsEmpty verifies that listing debug sessions for a backend with no +// TestAdminDebugListSessionsEmpty verifies that listing debug sessions for a backend with no // sessions returns 200 with an empty list. -func TestDebugListSessionsEmpty(t *testing.T) { +func TestAdminDebugListSessionsEmpty(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -115,8 +115,8 @@ func TestDebugListSessionsEmpty(t *testing.T) { } } -// TestDebugListSessionsContainsCreated verifies that a created session appears in the list. -func TestDebugListSessionsContainsCreated(t *testing.T) { +// TestAdminDebugListSessionsContainsCreated verifies that a created session appears in the list. +func TestAdminDebugListSessionsContainsCreated(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -153,8 +153,8 @@ func TestDebugListSessionsContainsCreated(t *testing.T) { // --- GetDebugSession --- -// TestDebugGetSession verifies that an existing session can be retrieved by ID. -func TestDebugGetSession(t *testing.T) { +// TestAdminDebugGetSession verifies that an existing session can be retrieved by ID. +func TestAdminDebugGetSession(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -189,8 +189,8 @@ func TestDebugGetSession(t *testing.T) { lib.VerifyStatusCode(deleteResp.StatusCode(), http.StatusNoContent, t) } -// TestDebugGetSessionNotFound verifies that requesting a non-existent session returns 404. -func TestDebugGetSessionNotFound(t *testing.T) { +// TestAdminDebugGetSessionNotFound verifies that requesting a non-existent session returns 404. +func TestAdminDebugGetSessionNotFound(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -207,8 +207,8 @@ func TestDebugGetSessionNotFound(t *testing.T) { // --- ExtendDebugSession --- -// TestDebugExtendSession verifies that extending a session updates ExpiresAt. -func TestDebugExtendSession(t *testing.T) { +// TestAdminDebugExtendSession verifies that extending a session updates ExpiresAt. +func TestAdminDebugExtendSession(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -253,8 +253,8 @@ func TestDebugExtendSession(t *testing.T) { lib.VerifyStatusCode(deleteResp.StatusCode(), http.StatusNoContent, t) } -// TestDebugExtendSessionNotFound verifies that extending a non-existent session returns 404. -func TestDebugExtendSessionNotFound(t *testing.T) { +// TestAdminDebugExtendSessionNotFound verifies that extending a non-existent session returns 404. +func TestAdminDebugExtendSessionNotFound(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -272,9 +272,9 @@ func TestDebugExtendSessionNotFound(t *testing.T) { // --- StopDebugSession --- -// TestDebugStopSession verifies that stopping an active session returns 204 and marks +// TestAdminDebugStopSession verifies that stopping an active session returns 204 and marks // the session as stopped. -func TestDebugStopSession(t *testing.T) { +func TestAdminDebugStopSession(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -312,8 +312,8 @@ func TestDebugStopSession(t *testing.T) { lib.VerifyStatusCode(deleteResp.StatusCode(), http.StatusNoContent, t) } -// TestDebugStopSessionNotFound verifies that stopping a non-existent session returns 404. -func TestDebugStopSessionNotFound(t *testing.T) { +// TestAdminDebugStopSessionNotFound verifies that stopping a non-existent session returns 404. +func TestAdminDebugStopSessionNotFound(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -330,9 +330,9 @@ func TestDebugStopSessionNotFound(t *testing.T) { // --- DeleteDebugSession --- -// TestDebugDeleteSession verifies that deleting a session returns 204 and the session +// TestAdminDebugDeleteSession verifies that deleting a session returns 204 and the session // is no longer retrievable. -func TestDebugDeleteSession(t *testing.T) { +func TestAdminDebugDeleteSession(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -357,8 +357,8 @@ func TestDebugDeleteSession(t *testing.T) { lib.VerifyStatusCode(getResp.StatusCode(), http.StatusNotFound, t) } -// TestDebugDeleteSessionNotFound verifies that deleting a non-existent session returns 404. -func TestDebugDeleteSessionNotFound(t *testing.T) { +// TestAdminDebugDeleteSessionNotFound verifies that deleting a non-existent session returns 404. +func TestAdminDebugDeleteSessionNotFound(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -375,10 +375,10 @@ func TestDebugDeleteSessionNotFound(t *testing.T) { // --- ListDebugSessionCalls & GetDebugSessionCall --- -// TestDebugListSessionCallsWithTransitions verifies that after a gateway request is made +// TestAdminDebugListSessionCallsWithTransitions verifies that after a gateway request is made // during an active debug session, the call is recorded and flow transitions are populated // when includeTransitions=true. -func TestDebugListSessionCallsWithTransitions(t *testing.T) { +func TestAdminDebugListSessionCallsWithTransitions(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -417,9 +417,9 @@ func TestDebugListSessionCallsWithTransitions(t *testing.T) { } } -// TestDebugListSessionCallsWithoutTransitions verifies that when includeTransitions=false, +// TestAdminDebugListSessionCallsWithoutTransitions verifies that when includeTransitions=false, // FlowTransitions are not included in the response. -func TestDebugListSessionCallsWithoutTransitions(t *testing.T) { +func TestAdminDebugListSessionCallsWithoutTransitions(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -459,8 +459,8 @@ func TestDebugListSessionCallsWithoutTransitions(t *testing.T) { } } -// TestDebugGetSessionCall verifies that a specific recorded call can be retrieved by ID. -func TestDebugGetSessionCall(t *testing.T) { +// TestAdminDebugGetSessionCall verifies that a specific recorded call can be retrieved by ID. +func TestAdminDebugGetSessionCall(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -516,8 +516,8 @@ func TestDebugGetSessionCall(t *testing.T) { } } -// TestDebugGetSessionCallNotFound verifies that requesting a non-existent call returns 404. -func TestDebugGetSessionCallNotFound(t *testing.T) { +// TestAdminDebugGetSessionCallNotFound verifies that requesting a non-existent call returns 404. +func TestAdminDebugGetSessionCallNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) sessionID := lib.StartDebugSession(t, superRequestEditor, "echo") @@ -546,9 +546,9 @@ func TestDebugGetSessionCallNotFound(t *testing.T) { // --- Full lifecycle --- -// TestDebugFullFlow exercises the complete debug session lifecycle end-to-end: +// TestAdminDebugFullFlow exercises the complete debug session lifecycle end-to-end: // start → get → hit gateway (records a call) → list calls → get call → stop → delete. -func TestDebugFullFlow(t *testing.T) { +func TestAdminDebugFullFlow(t *testing.T) { superRequestEditor := lib.SuperLogin(t) // Start. diff --git a/test/suites/integration/admin_api_flow_test.go b/test/suites/integration/admin_api_flow_test.go new file mode 100644 index 0000000..02acdb0 --- /dev/null +++ b/test/suites/integration/admin_api_flow_test.go @@ -0,0 +1,114 @@ +package integration + +import ( + lib "github.com/trebent/kerberos/test/lib" + "net/http" + "testing" + + adminapi "github.com/trebent/kerberos/test/client/admin" +) + +func TestAdminGetFlow(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + getFlowResp, err := lib.AdminClient.GetFlowWithResponse( + t.Context(), + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getFlowResp.StatusCode(), http.StatusOK, t) + + for i, component := range *getFlowResp.JSON200 { + t.Logf("Flow component index: %d name: %s", i, component.Name) + + switch component.Name { + case "obs": + if i != 0 { + t.Error("observability component should have index 0") + } + _, err := component.Data.AsFlowMetaDataObservability() + if err != nil { + t.Fatalf("Failed to parse observability component data: %v", err) + } + case "router": + if i != 1 { + t.Error("router component should have index 1") + } + _, err := component.Data.AsFlowMetaDataRouter() + if err != nil { + t.Fatalf("Failed to parse router component data: %v", err) + } + case "authorizer": + if i != 2 { + t.Error("authorizer component should have index 2") + } + _, err := component.Data.AsFlowMetaDataAuth() + if err != nil { + t.Fatalf("Failed to parse authorizer component data: %v", err) + } + case "oas-validator": + if i != 3 { + t.Error("oas-validator component should have index 3") + } + _, err := component.Data.AsFlowMetaDataOAS() + if err != nil { + t.Fatalf("Failed to parse oas-validator component data: %v", err) + } + case "forwarder": + if i != 4 { + t.Error("forwarder component should have index 4") + } + _, err := component.Data.AsNoFlowMetaData() + if err != nil { + t.Fatalf("Failed to parse forwarder component data: %v", err) + } + default: + t.Errorf("Unexpected flow component name: %s", component.Name) + } + } +} + +// TestAdminGetFlowAsAdminUser verifies that a non-superuser admin user can also access the GetFlow endpoint. +func TestAdminGetFlowAsAdminUser(t *testing.T) { + superRequestEditor := lib.SuperLogin(t) + + name := lib.Username() + const pass = "password123" + createResp, err := lib.AdminClient.CreateUserWithResponse( + t.Context(), + adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + + userID := lib.MustGetAdminUserID(t, superRequestEditor, name) + + // Create a group with the flowviewer permission and assign the user to it. + grpResp, err := lib.AdminClient.CreateGroupWithResponse( + t.Context(), + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(grpResp.StatusCode(), http.StatusCreated, t) + + updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( + t.Context(), + userID, + adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grpResp.JSON201.Id}}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) + + adminRequestEditor := lib.AdminUserLogin(t, name, pass) + + getFlowResp, err := lib.AdminClient.GetFlowWithResponse( + t.Context(), + adminapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getFlowResp.StatusCode(), http.StatusOK, t) +} diff --git a/test/suites/integration/admin_api_group_bindings_test.go b/test/suites/integration/admin_api_group_bindings_test.go deleted file mode 100644 index 57dab3a..0000000 --- a/test/suites/integration/admin_api_group_bindings_test.go +++ /dev/null @@ -1,232 +0,0 @@ -package integration - -import ( - lib "github.com/trebent/kerberos/test/lib" - "net/http" - "testing" - - adminapi "github.com/trebent/kerberos/test/client/admin" -) - -// TestAdminUserGroupBindingsAssign verifies that a user can be assigned to groups, -// and that those groups are reflected in the GetUser response. -func TestAdminUserGroupBindingsAssign(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - name := lib.Username() - createUserResp, err := lib.AdminClient.CreateUserWithResponse( - t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: "password123"}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) - userID := lib.MustGetAdminUserID(t, superRequestEditor, name) - - grp1Resp, err := lib.AdminClient.CreateGroupWithResponse( - t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(grp1Resp.StatusCode(), http.StatusCreated, t) - grp1ID := grp1Resp.JSON201.Id - - grp2Resp, err := lib.AdminClient.CreateGroupWithResponse( - t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(grp2Resp.StatusCode(), http.StatusCreated, t) - grp2ID := grp2Resp.JSON201.Id - - updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( - t.Context(), - userID, - adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grp1ID, grp2ID}}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - - getResp, err := lib.AdminClient.GetUserWithResponse( - t.Context(), - userID, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) - if getResp.JSON200.Groups == nil { - t.Fatal("expected non-nil groups on user") - } - if len(*getResp.JSON200.Groups) != 2 { - t.Fatalf("expected 2 groups, got %d", len(*getResp.JSON200.Groups)) - } - groupIDs := make([]int, 0, len(*getResp.JSON200.Groups)) - for _, g := range *getResp.JSON200.Groups { - groupIDs = append(groupIDs, g.Id) - } - lib.ContainsAll([]int{grp1ID, grp2ID}, groupIDs, t) -} - -// TestAdminUserGroupBindingsUpdate verifies that a user's group membership can be partially updated -// (groups removed and added). -func TestAdminUserGroupBindingsUpdate(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - name := lib.Username() - createUserResp, err := lib.AdminClient.CreateUserWithResponse( - t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: "password123"}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) - userID := lib.MustGetAdminUserID(t, superRequestEditor, name) - - grp1Resp, err := lib.AdminClient.CreateGroupWithResponse( - t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(grp1Resp.StatusCode(), http.StatusCreated, t) - grp1ID := grp1Resp.JSON201.Id - - grp2Resp, err := lib.AdminClient.CreateGroupWithResponse( - t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(grp2Resp.StatusCode(), http.StatusCreated, t) - grp2ID := grp2Resp.JSON201.Id - - grp3Resp, err := lib.AdminClient.CreateGroupWithResponse( - t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(grp3Resp.StatusCode(), http.StatusCreated, t) - grp3ID := grp3Resp.JSON201.Id - - // Assign to grp1 and grp2. - updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( - t.Context(), - userID, - adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grp1ID, grp2ID}}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - - // Update: remove grp1, add grp3. - updateResp, err = lib.AdminClient.UpdateUserGroupsWithResponse( - t.Context(), - userID, - adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grp2ID, grp3ID}}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - - getResp, err := lib.AdminClient.GetUserWithResponse( - t.Context(), - userID, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) - if getResp.JSON200.Groups == nil { - t.Fatal("expected non-nil groups on user") - } - if len(*getResp.JSON200.Groups) != 2 { - t.Fatalf("expected 2 groups after update, got %d", len(*getResp.JSON200.Groups)) - } - groupIDs := make([]int, 0, len(*getResp.JSON200.Groups)) - for _, g := range *getResp.JSON200.Groups { - groupIDs = append(groupIDs, g.Id) - } - lib.ContainsAll([]int{grp2ID, grp3ID}, groupIDs, t) - for _, id := range groupIDs { - if id == grp1ID { - t.Fatalf("grp1 should have been removed from user groups") - } - } -} - -// TestAdminUserGroupBindingsClear verifies that a user's group memberships can be cleared. -func TestAdminUserGroupBindingsClear(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - name := lib.Username() - createUserResp, err := lib.AdminClient.CreateUserWithResponse( - t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: "password123"}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) - userID := lib.MustGetAdminUserID(t, superRequestEditor, name) - - grpResp, err := lib.AdminClient.CreateGroupWithResponse( - t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(grpResp.StatusCode(), http.StatusCreated, t) - grpID := grpResp.JSON201.Id - - // Assign to the group. - updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( - t.Context(), - userID, - adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grpID}}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - - // Clear all groups. - updateResp, err = lib.AdminClient.UpdateUserGroupsWithResponse( - t.Context(), - userID, - adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{}}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - - getResp, err := lib.AdminClient.GetUserWithResponse( - t.Context(), - userID, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) - if getResp.JSON200.Groups != nil && len(*getResp.JSON200.Groups) != 0 { - t.Fatalf("expected 0 groups after clear, got %d", len(*getResp.JSON200.Groups)) - } -} - -// TestAdminUserGroupBindingsNotFoundUser verifies that updating groups for a non-existent user returns 404. -func TestAdminUserGroupBindingsNotFoundUser(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( - t.Context(), - 999999999, - adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{}}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNotFound, t) - lib.VerifyAdminAPIErrorResponse(updateResp.JSON404, t) -} diff --git a/test/suites/integration/admin_api_oas_test.go b/test/suites/integration/admin_api_oas_test.go new file mode 100644 index 0000000..8f19719 --- /dev/null +++ b/test/suites/integration/admin_api_oas_test.go @@ -0,0 +1,81 @@ +package integration + +import ( + lib "github.com/trebent/kerberos/test/lib" + "net/http" + "testing" + + adminapi "github.com/trebent/kerberos/test/client/admin" +) + +func TestAdminGetBackendOASNotFound(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + resp, err := lib.AdminClient.GetBackendOASWithResponse( + t.Context(), + "nonexistent-backend", + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(resp.StatusCode(), http.StatusNotFound, t) + lib.VerifyAdminAPIErrorResponse(resp.JSON404, t) +} + +func TestAdminGetBackendOAS(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + getBackendOASResp, err := lib.AdminClient.GetBackendOASWithResponse( + t.Context(), + "echo", + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getBackendOASResp.StatusCode(), http.StatusOK, t) +} + +// TestAdminGetBackendOASAsAdminUser verifies that a non-superuser admin user can also access the GetBackendOAS endpoint. +func TestAdminGetBackendOASAsAdminUser(t *testing.T) { + superRequestEditor := lib.SuperLogin(t) + + name := lib.Username() + const pass = "password123" + createResp, err := lib.AdminClient.CreateUserWithResponse( + t.Context(), + adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + + userID := lib.MustGetAdminUserID(t, superRequestEditor, name) + + // Create a group with the oasviewer permission and assign the user to it. + grpResp, err := lib.AdminClient.CreateGroupWithResponse( + t.Context(), + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(grpResp.StatusCode(), http.StatusCreated, t) + + updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( + t.Context(), + userID, + adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grpResp.JSON201.Id}}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) + + adminRequestEditor := lib.AdminUserLogin(t, name, pass) + + getBackendOASResp, err := lib.AdminClient.GetBackendOASWithResponse( + t.Context(), + "echo", + adminapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getBackendOASResp.StatusCode(), http.StatusOK, t) +} diff --git a/test/suites/integration/admin_api_permissions_test.go b/test/suites/integration/admin_api_permissions_test.go index 0f21fba..d77265d 100644 --- a/test/suites/integration/admin_api_permissions_test.go +++ b/test/suites/integration/admin_api_permissions_test.go @@ -32,9 +32,9 @@ const ( // --- GetPermissions --- -// TestPermissionsGetPermissions verifies that any authenticated admin user can list +// TestAdminPermissionsGetPermissions verifies that any authenticated admin user can list // available permissions. -func TestPermissionsGetPermissions(t *testing.T) { +func TestAdminPermissionsGetPermissions(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -73,9 +73,9 @@ func TestPermissionsGetPermissions(t *testing.T) { // --- Superuser access --- -// TestPermissionsSuperuserAccessAll verifies that the superuser can access every +// TestAdminPermissionsSuperuserAccessAll verifies that the superuser can access every // permission-gated endpoint without being a member of any group. -func TestPermissionsSuperuserAccessAll(t *testing.T) { +func TestAdminPermissionsSuperuserAccessAll(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -164,9 +164,9 @@ func TestPermissionsSuperuserAccessAll(t *testing.T) { // --- flowviewer permission --- -// TestPermissionsFlowViewerAllowed verifies that an admin user with the flowviewer +// TestAdminPermissionsFlowViewerAllowed verifies that an admin user with the flowviewer // permission can call GetFlow. -func TestPermissionsFlowViewerAllowed(t *testing.T) { +func TestAdminPermissionsFlowViewerAllowed(t *testing.T) { t.Parallel() superSession := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superSession, []int{PermissionIDFlowViewer}) @@ -179,9 +179,9 @@ func TestPermissionsFlowViewerAllowed(t *testing.T) { lib.VerifyStatusCode(resp.StatusCode(), http.StatusOK, t) } -// TestPermissionsFlowViewerDeniedWithoutPermission verifies that an admin user without +// TestAdminPermissionsFlowViewerDeniedWithoutPermission verifies that an admin user without // the flowviewer permission receives 403 when calling GetFlow. -func TestPermissionsFlowViewerDeniedWithoutPermission(t *testing.T) { +func TestAdminPermissionsFlowViewerDeniedWithoutPermission(t *testing.T) { t.Parallel() superSession := lib.SuperLogin(t) // Give only oasviewer — no flowviewer. @@ -195,9 +195,9 @@ func TestPermissionsFlowViewerDeniedWithoutPermission(t *testing.T) { lib.VerifyStatusCode(resp.StatusCode(), http.StatusForbidden, t) } -// TestPermissionsFlowViewerDeniedNoGroup verifies that an admin user in no group at all +// TestAdminPermissionsFlowViewerDeniedNoGroup verifies that an admin user in no group at all // receives 403 when calling GetFlow. -func TestPermissionsFlowViewerDeniedNoGroup(t *testing.T) { +func TestAdminPermissionsFlowViewerDeniedNoGroup(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -223,9 +223,9 @@ func TestPermissionsFlowViewerDeniedNoGroup(t *testing.T) { // --- oasviewer permission --- -// TestPermissionsOASViewerAllowed verifies that an admin user with the oasviewer +// TestAdminPermissionsOASViewerAllowed verifies that an admin user with the oasviewer // permission can call GetBackendOAS. -func TestPermissionsOASViewerAllowed(t *testing.T) { +func TestAdminPermissionsOASViewerAllowed(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superRequestEditor, []int{PermissionIDOASViewer}) @@ -239,9 +239,9 @@ func TestPermissionsOASViewerAllowed(t *testing.T) { lib.VerifyStatusCode(resp.StatusCode(), http.StatusOK, t) } -// TestPermissionsOASViewerDeniedWithoutPermission verifies that an admin user without +// TestAdminPermissionsOASViewerDeniedWithoutPermission verifies that an admin user without // the oasviewer permission receives 403 when calling GetBackendOAS. -func TestPermissionsOASViewerDeniedWithoutPermission(t *testing.T) { +func TestAdminPermissionsOASViewerDeniedWithoutPermission(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) // Give only flowviewer — no oasviewer. @@ -258,10 +258,10 @@ func TestPermissionsOASViewerDeniedWithoutPermission(t *testing.T) { // --- basicauthorgadmin permission --- -// TestPermissionsBasicAuthOrgAdminAllowed verifies that an admin user with the +// TestAdminPermissionsBasicAuthOrgAdminAllowed verifies that an admin user with the // basicauthorgadmin permission can perform both read and write operations on the // basic auth API. -func TestPermissionsBasicAuthOrgAdminAllowed(t *testing.T) { +func TestAdminPermissionsBasicAuthOrgAdminAllowed(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superRequestEditor, []int{PermissionIDBasicAuthOrgAdmin}) @@ -297,10 +297,10 @@ func TestPermissionsBasicAuthOrgAdminAllowed(t *testing.T) { lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) } -// TestPermissionsBasicAuthOrgAdminDeniedWithoutPermission verifies that an admin user +// TestAdminPermissionsBasicAuthOrgAdminDeniedWithoutPermission verifies that an admin user // without any basic auth permission cannot access the basic auth API. The middleware falls // through to session lookup (which does not recognise an admin session), returning 401. -func TestPermissionsBasicAuthOrgAdminDeniedWithoutPermission(t *testing.T) { +func TestAdminPermissionsBasicAuthOrgAdminDeniedWithoutPermission(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) // Give only flowviewer — no basic auth permission. @@ -318,9 +318,9 @@ func TestPermissionsBasicAuthOrgAdminDeniedWithoutPermission(t *testing.T) { // --- basicauthorgviewer permission --- -// TestPermissionsBasicAuthOrgViewerReadAllowed verifies that an admin user with the +// TestAdminPermissionsBasicAuthOrgViewerReadAllowed verifies that an admin user with the // basicauthorgviewer permission can call GET endpoints on the basic auth API. -func TestPermissionsBasicAuthOrgViewerReadAllowed(t *testing.T) { +func TestAdminPermissionsBasicAuthOrgViewerReadAllowed(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -348,10 +348,10 @@ func TestPermissionsBasicAuthOrgViewerReadAllowed(t *testing.T) { lib.VerifyStatusCode(listGroupsResp.StatusCode(), http.StatusOK, t) } -// TestPermissionsBasicAuthOrgViewerWriteDenied verifies that an admin user with the +// TestAdminPermissionsBasicAuthOrgViewerWriteDenied verifies that an admin user with the // basicauthorgviewer permission is denied for non-GET (write) endpoints on the basic // auth API. -func TestPermissionsBasicAuthOrgViewerWriteDenied(t *testing.T) { +func TestAdminPermissionsBasicAuthOrgViewerWriteDenied(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superRequestEditor, []int{PermissionIDBasicAuthOrgViewer}) @@ -377,11 +377,11 @@ func TestPermissionsBasicAuthOrgViewerWriteDenied(t *testing.T) { lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusForbidden, t) } -// TestPermissionsBasicAuthOrgViewerDeniedWithoutPermission verifies that an admin user +// TestAdminPermissionsBasicAuthOrgViewerDeniedWithoutPermission verifies that an admin user // with no basic auth permission cannot access even GET endpoints on the basic auth API. // The middleware falls through to session lookup (which does not recognise an admin session), // returning 401. -func TestPermissionsBasicAuthOrgViewerDeniedWithoutPermission(t *testing.T) { +func TestAdminPermissionsBasicAuthOrgViewerDeniedWithoutPermission(t *testing.T) { t.Parallel() superSession := lib.SuperLogin(t) orgID, _ := lib.OrgWithSession(t, superSession) @@ -400,9 +400,9 @@ func TestPermissionsBasicAuthOrgViewerDeniedWithoutPermission(t *testing.T) { // --- Group response includes permissions --- -// TestPermissionsGroupResponseIncludesPermissions verifies that the permissions field is +// TestAdminPermissionsGroupResponseIncludesPermissions verifies that the permissions field is // present and accurate in the group create/get responses. -func TestPermissionsGroupResponseIncludesPermissions(t *testing.T) { +func TestAdminPermissionsGroupResponseIncludesPermissions(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -449,10 +449,10 @@ func TestPermissionsGroupResponseIncludesPermissions(t *testing.T) { // --- adminusermgmtadmin permission --- -// TestPermissionsAdminUserMgmtAdminAllowed verifies that an admin user with the +// TestAdminPermissionsAdminUserMgmtAdminAllowed verifies that an admin user with the // adminusermgmtadmin permission can perform both read and write operations on the // admin user and group management endpoints. -func TestPermissionsAdminUserMgmtAdminAllowed(t *testing.T) { +func TestAdminPermissionsAdminUserMgmtAdminAllowed(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superRequestEditor, []int{PermissionIDAdminUserMgmtAdmin}) @@ -549,9 +549,9 @@ func TestPermissionsAdminUserMgmtAdminAllowed(t *testing.T) { // --- adminusermgmtviewer permission --- -// TestPermissionsAdminUserMgmtViewerReadAllowed verifies that an admin user with the +// TestAdminPermissionsAdminUserMgmtViewerReadAllowed verifies that an admin user with the // adminusermgmtviewer permission can call GET endpoints on the admin user/group mgmt API. -func TestPermissionsAdminUserMgmtViewerReadAllowed(t *testing.T) { +func TestAdminPermissionsAdminUserMgmtViewerReadAllowed(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superRequestEditor, []int{PermissionIDAdminUserMgmtViewer}) @@ -573,10 +573,10 @@ func TestPermissionsAdminUserMgmtViewerReadAllowed(t *testing.T) { lib.VerifyStatusCode(listGroupsResp.StatusCode(), http.StatusOK, t) } -// TestPermissionsAdminUserMgmtViewerWriteDenied verifies that an admin user with the +// TestAdminPermissionsAdminUserMgmtViewerWriteDenied verifies that an admin user with the // adminusermgmtviewer permission is denied for non-GET (write) endpoints on the admin // user/group mgmt API. -func TestPermissionsAdminUserMgmtViewerWriteDenied(t *testing.T) { +func TestAdminPermissionsAdminUserMgmtViewerWriteDenied(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superRequestEditor, []int{PermissionIDAdminUserMgmtViewer}) @@ -600,9 +600,9 @@ func TestPermissionsAdminUserMgmtViewerWriteDenied(t *testing.T) { lib.VerifyStatusCode(createGroupResp.StatusCode(), http.StatusForbidden, t) } -// TestPermissionsAdminUserMgmtViewerDeniedWithoutPermission verifies that an admin user +// TestAdminPermissionsAdminUserMgmtViewerDeniedWithoutPermission verifies that an admin user // with no user mgmt permission receives 403 when calling even GET user mgmt endpoints. -func TestPermissionsAdminUserMgmtViewerDeniedWithoutPermission(t *testing.T) { +func TestAdminPermissionsAdminUserMgmtViewerDeniedWithoutPermission(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) // Give only flowviewer — no user mgmt permission. @@ -616,9 +616,9 @@ func TestPermissionsAdminUserMgmtViewerDeniedWithoutPermission(t *testing.T) { lib.VerifyStatusCode(listUsersResp.StatusCode(), http.StatusForbidden, t) } -// TestPermissionsAdminUserMgmtViewerGetSelf verifies that an admin user +// TestAdminPermissionsAdminUserMgmtViewerGetSelf verifies that an admin user // with no user mgmt permission can still get their own user information. -func TestPermissionsAdminUserMgmtViewerGetSelf(t *testing.T) { +func TestAdminPermissionsAdminUserMgmtViewerGetSelf(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -644,9 +644,9 @@ func TestPermissionsAdminUserMgmtViewerGetSelf(t *testing.T) { lib.Matches(listUsersResp.JSON200.Id, createResp.JSON201.Id, t) } -// TestPermissionsNormalUserLogoutSuper verifies that a normal admin user, even with permissions to call the logout +// TestAdminPermissionsNormalUserLogoutSuper verifies that a normal admin user, even with permissions to call the logout // endpoint, cannot log out the superuser. -func TestPermissionsNormalUserLogoutSuper(t *testing.T) { +func TestAdminPermissionsNormalUserLogoutSuper(t *testing.T) { superRequestEditor := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superRequestEditor, []int{PermissionIDAdminUserMgmtViewer}) @@ -659,9 +659,9 @@ func TestPermissionsNormalUserLogoutSuper(t *testing.T) { lib.VerifyStatusCode(logoutResp.StatusCode(), http.StatusForbidden, t) } -// TestPermissionsAdminUserChangePasswordWrongUser verifies that an admin user cannot change another user's +// TestAdminPermissionsAdminUserChangePasswordWrongUser verifies that an admin user cannot change another user's // password without the appropriate permission. -func TestPermissionsAdminUserChangePasswordWrongUser(t *testing.T) { +func TestAdminPermissionsAdminUserChangePasswordWrongUser(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) @@ -700,9 +700,9 @@ func TestPermissionsAdminUserChangePasswordWrongUser(t *testing.T) { // --- debugger permission --- -// TestPermissionsDebuggerAllowed verifies that an admin user with the debugger permission +// TestAdminPermissionsDebuggerAllowed verifies that an admin user with the debugger permission // can call StartDebugSession. -func TestPermissionsDebuggerAllowed(t *testing.T) { +func TestAdminPermissionsDebuggerAllowed(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) adminRequestEditor := lib.CreateAdminUserInGroup(t, superRequestEditor, []int{PermissionIDDebugger}) @@ -727,9 +727,9 @@ func TestPermissionsDebuggerAllowed(t *testing.T) { lib.VerifyStatusCode(deleteResp.StatusCode(), http.StatusNoContent, t) } -// TestPermissionsDebuggerDenied verifies that an admin user without the debugger permission +// TestAdminPermissionsDebuggerDenied verifies that an admin user without the debugger permission // receives 403 when calling StartDebugSession. -func TestPermissionsDebuggerDenied(t *testing.T) { +func TestAdminPermissionsDebuggerDenied(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) // Give only flowviewer — no debugger. diff --git a/test/suites/integration/admin_api_session_test.go b/test/suites/integration/admin_api_session_test.go deleted file mode 100644 index bb8ed68..0000000 --- a/test/suites/integration/admin_api_session_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package integration - -import ( - lib "github.com/trebent/kerberos/test/lib" - "net/http" - "testing" - - adminapi "github.com/trebent/kerberos/test/client/admin" -) - -// TestAdminRefreshSuperuserSessionNoRefreshCookie verifies that calling the superuser refresh -// endpoint without a refresh cookie returns 401. A missing session cookie alone is not enough -// to trigger an error — only the missing refresh cookie matters here. -func TestAdminRefreshSuperuserSessionNoRefreshCookie(t *testing.T) { - t.Parallel() - resp, err := lib.AdminClient.RefreshSuperuserSessionWithResponse(t.Context()) - lib.CheckErr(err, t) - lib.VerifyStatusCode(resp.StatusCode(), http.StatusUnauthorized, t) - lib.VerifyAdminAPIErrorResponse(resp.JSON401, t) -} - -// TestAdminRefreshSuperuserSession verifies that the superuser refresh endpoint issues a new -// session when called with only the refresh cookie (no session cookie required). -func TestAdminRefreshSuperuserSession(t *testing.T) { - t.Parallel() - loginResp, err := lib.AdminClient.LoginSuperuserWithResponse( - t.Context(), - adminapi.LoginSuperuserJSONRequestBody{ - ClientId: lib.SuperUserClientID, - ClientSecret: lib.SuperUserClientSecret, - }, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) - - // Use only the refresh cookie — deliberately omit the session cookie to prove it is not required. - refreshEditor := lib.RefreshCookieRequestEditor(loginResp.HTTPResponse, t) - - refreshResp, err := lib.AdminClient.RefreshSuperuserSessionWithResponse( - t.Context(), - adminapi.RequestEditorFn(refreshEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(refreshResp.StatusCode(), http.StatusNoContent, t) -} - -// TestAdminRefreshSuperuserSessionForbidden verifies that a non-superuser admin refresh token -// is rejected by the superuser refresh endpoint with 403. -func TestAdminRefreshSuperuserSessionForbidden(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - name := lib.Username() - const pass = "password123" - createResp, err := lib.AdminClient.CreateUserWithResponse( - t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) - - // Login as the regular admin user to get a non-superuser refresh token. - loginResp, err := lib.AdminClient.LoginWithResponse( - t.Context(), - adminapi.LoginJSONRequestBody{Username: name, Password: pass}, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) - - // Use only the refresh cookie from the regular user session. - userRefreshEditor := lib.RefreshCookieRequestEditor(loginResp.HTTPResponse, t) - - refreshResp, err := lib.AdminClient.RefreshSuperuserSessionWithResponse( - t.Context(), - adminapi.RequestEditorFn(userRefreshEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(refreshResp.StatusCode(), http.StatusForbidden, t) - lib.VerifyAdminAPIErrorResponse(refreshResp.JSON403, t) -} - -// TestAdminRefreshUserSessionNoRefreshCookie verifies that calling the admin user refresh -// endpoint without a refresh cookie returns 401. -func TestAdminRefreshUserSessionNoRefreshCookie(t *testing.T) { - t.Parallel() - resp, err := lib.AdminClient.RefreshUserSessionWithResponse(t.Context()) - lib.CheckErr(err, t) - lib.VerifyStatusCode(resp.StatusCode(), http.StatusUnauthorized, t) - lib.VerifyAdminAPIErrorResponse(resp.JSON401, t) -} - -// TestAdminRefreshUserSession verifies that the admin user refresh endpoint issues a new -// session when called with only the refresh cookie (no session cookie required). -func TestAdminRefreshUserSession(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - name := lib.Username() - const pass = "password123" - createResp, err := lib.AdminClient.CreateUserWithResponse( - t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) - - loginResp, err := lib.AdminClient.LoginWithResponse( - t.Context(), - adminapi.LoginJSONRequestBody{Username: name, Password: pass}, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) - - // Use only the refresh cookie — deliberately omit the session cookie. - refreshEditor := lib.RefreshCookieRequestEditor(loginResp.HTTPResponse, t) - - refreshResp, err := lib.AdminClient.RefreshUserSessionWithResponse( - t.Context(), - adminapi.RequestEditorFn(refreshEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(refreshResp.StatusCode(), http.StatusNoContent, t) -} diff --git a/test/suites/integration/admin_api_test.go b/test/suites/integration/admin_api_test.go deleted file mode 100644 index 00b20f7..0000000 --- a/test/suites/integration/admin_api_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package integration - -import ( - lib "github.com/trebent/kerberos/test/lib" - "net/http" - "testing" - - adminapi "github.com/trebent/kerberos/test/client/admin" -) - -// allPermissionIDs is the base set of all available admin group permissions. -// Tests that create admin groups should include these to avoid breaking permission-gated endpoints. -var allPermissionIDs = []int{1, 2, 3, 4, 5, 6, 7} - -func TestAdminLoginSuperuser(t *testing.T) { - superRequestEditor := lib.SuperLogin(t) - - t.Log("Logging the superuser out") - superLogoutResp, err := lib.AdminClient.LogoutSuperuserWithResponse( - t.Context(), - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(superLogoutResp.StatusCode(), http.StatusNoContent, t) - - t.Log("Running a GET flow request with the old session to verify it is invalidated") - // Verify the old session is truly invalidated by attempting to access a protected endpoint with it. - getFlowResp, err := lib.AdminClient.GetFlowWithResponse( - t.Context(), - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getFlowResp.StatusCode(), http.StatusUnauthorized, t) -} - -func TestAdminLoginSuperuserFailure(t *testing.T) { - t.Parallel() - superLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse( - t.Context(), - adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: "not-correct"}, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(superLoginResp.StatusCode(), http.StatusUnauthorized, t) - lib.VerifyAdminAPIErrorResponse(superLoginResp.JSON401, t) -} - -func TestAdminOASFailure(t *testing.T) { - t.Parallel() - badSuperLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse(t.Context(), adminapi.LoginSuperuserJSONRequestBody{}) - lib.CheckErr(err, t) - lib.VerifyStatusCode(badSuperLoginResp.StatusCode(), http.StatusBadRequest, t) - lib.VerifyAdminAPIErrorResponse(badSuperLoginResp.JSON400, t) -} - -func TestAdminGetFlow(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - getFlowResp, err := lib.AdminClient.GetFlowWithResponse( - t.Context(), - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getFlowResp.StatusCode(), http.StatusOK, t) - - for i, component := range *getFlowResp.JSON200 { - t.Logf("Flow component index: %d name: %s", i, component.Name) - - switch component.Name { - case "obs": - if i != 0 { - t.Error("observability component should have index 0") - } - _, err := component.Data.AsFlowMetaDataObservability() - if err != nil { - t.Fatalf("Failed to parse observability component data: %v", err) - } - case "router": - if i != 1 { - t.Error("router component should have index 1") - } - _, err := component.Data.AsFlowMetaDataRouter() - if err != nil { - t.Fatalf("Failed to parse router component data: %v", err) - } - case "authorizer": - if i != 2 { - t.Error("authorizer component should have index 2") - } - _, err := component.Data.AsFlowMetaDataAuth() - if err != nil { - t.Fatalf("Failed to parse authorizer component data: %v", err) - } - case "oas-validator": - if i != 3 { - t.Error("oas-validator component should have index 3") - } - _, err := component.Data.AsFlowMetaDataOAS() - if err != nil { - t.Fatalf("Failed to parse oas-validator component data: %v", err) - } - case "forwarder": - if i != 4 { - t.Error("forwarder component should have index 4") - } - _, err := component.Data.AsNoFlowMetaData() - if err != nil { - t.Fatalf("Failed to parse forwarder component data: %v", err) - } - default: - t.Errorf("Unexpected flow component name: %s", component.Name) - } - } -} - -func TestAdminGetBackendOASNotFound(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - resp, err := lib.AdminClient.GetBackendOASWithResponse( - t.Context(), - "nonexistent-backend", - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(resp.StatusCode(), http.StatusNotFound, t) - lib.VerifyAdminAPIErrorResponse(resp.JSON404, t) -} - -func TestAdminGetBackendOAS(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - getBackendOASResp, err := lib.AdminClient.GetBackendOASWithResponse( - t.Context(), - "echo", - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getBackendOASResp.StatusCode(), http.StatusOK, t) -} - -// TestAdminGetFlowAsAdminUser verifies that a non-superuser admin user can also access the GetFlow endpoint. -func TestAdminGetFlowAsAdminUser(t *testing.T) { - superRequestEditor := lib.SuperLogin(t) - - name := lib.Username() - const pass = "password123" - createResp, err := lib.AdminClient.CreateUserWithResponse( - t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) - - userID := lib.MustGetAdminUserID(t, superRequestEditor, name) - - // Create a group with the flowviewer permission and assign the user to it. - grpResp, err := lib.AdminClient.CreateGroupWithResponse( - t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(grpResp.StatusCode(), http.StatusCreated, t) - - updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( - t.Context(), - userID, - adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grpResp.JSON201.Id}}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - - adminRequestEditor := lib.AdminUserLogin(t, name, pass) - - getFlowResp, err := lib.AdminClient.GetFlowWithResponse( - t.Context(), - adminapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getFlowResp.StatusCode(), http.StatusOK, t) -} - -// TestAdminGetBackendOASAsAdminUser verifies that a non-superuser admin user can also access the GetBackendOAS endpoint. -func TestAdminGetBackendOASAsAdminUser(t *testing.T) { - superRequestEditor := lib.SuperLogin(t) - - name := lib.Username() - const pass = "password123" - createResp, err := lib.AdminClient.CreateUserWithResponse( - t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) - - userID := lib.MustGetAdminUserID(t, superRequestEditor, name) - - // Create a group with the oasviewer permission and assign the user to it. - grpResp, err := lib.AdminClient.CreateGroupWithResponse( - t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(grpResp.StatusCode(), http.StatusCreated, t) - - updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( - t.Context(), - userID, - adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grpResp.JSON201.Id}}, - adminapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - - adminRequestEditor := lib.AdminUserLogin(t, name, pass) - - getBackendOASResp, err := lib.AdminClient.GetBackendOASWithResponse( - t.Context(), - "echo", - adminapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getBackendOASResp.StatusCode(), http.StatusOK, t) -} diff --git a/test/suites/integration/admin_api_users_test.go b/test/suites/integration/admin_api_users_test.go index 2cb2a57..b69b747 100644 --- a/test/suites/integration/admin_api_users_test.go +++ b/test/suites/integration/admin_api_users_test.go @@ -227,188 +227,225 @@ func TestAdminUserDeleteNotFound(t *testing.T) { lib.VerifyAdminAPIErrorResponse(deleteResp.JSON404, t) } -// TestAdminUserLoginLogout verifies that an admin user can log in, access protected endpoints, -// log out, and that their session is invalidated afterwards. -func TestAdminUserLoginLogout(t *testing.T) { +// TestAdminUserGroupBindingsAssign verifies that a user can be assigned to groups, +// and that those groups are reflected in the GetUser response. +func TestAdminUserGroupBindingsAssign(t *testing.T) { + t.Parallel() superRequestEditor := lib.SuperLogin(t) name := lib.Username() - const pass = "loginpassword123" - createResp, err := lib.AdminClient.CreateUserWithResponse( + createUserResp, err := lib.AdminClient.CreateUserWithResponse( t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.CreateUserJSONRequestBody{Username: name, Password: "password123"}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) - - adminRequestEditor := lib.AdminUserLogin(t, name, pass) + lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) + userID := lib.MustGetAdminUserID(t, superRequestEditor, name) - // GetPermissions is accessible to any authenticated admin user (no specific permission required). - getPermsResp, err := lib.AdminClient.GetPermissionsWithResponse( + grp1Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.RequestEditorFn(adminRequestEditor), + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(getPermsResp.StatusCode(), http.StatusOK, t) + lib.VerifyStatusCode(grp1Resp.StatusCode(), http.StatusCreated, t) + grp1ID := grp1Resp.JSON201.Id - logoutResp, err := lib.AdminClient.LogoutWithResponse( + grp2Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.RequestEditorFn(adminRequestEditor), + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(logoutResp.StatusCode(), http.StatusNoContent, t) + lib.VerifyStatusCode(grp2Resp.StatusCode(), http.StatusCreated, t) + grp2ID := grp2Resp.JSON201.Id - getPermsResp, err = lib.AdminClient.GetPermissionsWithResponse( + updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( t.Context(), - adminapi.RequestEditorFn(adminRequestEditor), + userID, + adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grp1ID, grp2ID}}, + adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(getPermsResp.StatusCode(), http.StatusUnauthorized, t) -} + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) -// TestAdminUserLoginFailure verifies that login with incorrect credentials returns 401. -func TestAdminUserLoginFailure(t *testing.T) { - t.Parallel() - loginResp, err := lib.AdminClient.LoginWithResponse( + getResp, err := lib.AdminClient.GetUserWithResponse( t.Context(), - adminapi.LoginJSONRequestBody{Username: "no-such-user", Password: "wrong"}, + userID, + adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusUnauthorized, t) - lib.VerifyAdminAPIErrorResponse(loginResp.JSON401, t) + lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) + if getResp.JSON200.Groups == nil { + t.Fatal("expected non-nil groups on user") + } + if len(*getResp.JSON200.Groups) != 2 { + t.Fatalf("expected 2 groups, got %d", len(*getResp.JSON200.Groups)) + } + groupIDs := make([]int, 0, len(*getResp.JSON200.Groups)) + for _, g := range *getResp.JSON200.Groups { + groupIDs = append(groupIDs, g.Id) + } + lib.ContainsAll([]int{grp1ID, grp2ID}, groupIDs, t) } -// TestAdminUserChangePassword verifies that an admin user can change their password, -// that the old credentials are rejected, and that the new credentials work. -func TestAdminUserChangePassword(t *testing.T) { +// TestAdminUserGroupBindingsUpdate verifies that a user's group membership can be partially updated +// (groups removed and added). +func TestAdminUserGroupBindingsUpdate(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) name := lib.Username() - const oldPass = "oldpassword123" - const newPass = "newpassword456" - - createResp, err := lib.AdminClient.CreateUserWithResponse( + createUserResp, err := lib.AdminClient.CreateUserWithResponse( t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: oldPass}, + adminapi.CreateUserJSONRequestBody{Username: name, Password: "password123"}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) + userID := lib.MustGetAdminUserID(t, superRequestEditor, name) - adminRequestEditor := lib.AdminUserLogin(t, name, oldPass) - - changeResp, err := lib.AdminClient.ChangeUserPasswordWithResponse( + grp1Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - createResp.JSON201.Id, - adminapi.ChangeUserPasswordJSONRequestBody{OldPassword: oldPass, NewPassword: newPass}, - adminapi.RequestEditorFn(adminRequestEditor), + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(changeResp.StatusCode(), http.StatusNoContent, t) + lib.VerifyStatusCode(grp1Resp.StatusCode(), http.StatusCreated, t) + grp1ID := grp1Resp.JSON201.Id - oldLoginResp, err := lib.AdminClient.LoginWithResponse( + grp2Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.LoginJSONRequestBody{Username: name, Password: oldPass}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(oldLoginResp.StatusCode(), http.StatusUnauthorized, t) - - _ = lib.AdminUserLogin(t, name, newPass) -} + lib.VerifyStatusCode(grp2Resp.StatusCode(), http.StatusCreated, t) + grp2ID := grp2Resp.JSON201.Id -// TestAdminUserChangePasswordWrongOld verifies that providing the wrong old password is rejected. -func TestAdminUserChangePasswordWrongOld(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) + grp3Resp, err := lib.AdminClient.CreateGroupWithResponse( + t.Context(), + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(grp3Resp.StatusCode(), http.StatusCreated, t) + grp3ID := grp3Resp.JSON201.Id - name := lib.Username() - const pass = "correctpassword123" + // Assign to grp1 and grp2. + updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( + t.Context(), + userID, + adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grp1ID, grp2ID}}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - createResp, err := lib.AdminClient.CreateUserWithResponse( + // Update: remove grp1, add grp3. + updateResp, err = lib.AdminClient.UpdateUserGroupsWithResponse( t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + userID, + adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grp2ID, grp3ID}}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - changeResp, err := lib.AdminClient.ChangeUserPasswordWithResponse( + getResp, err := lib.AdminClient.GetUserWithResponse( t.Context(), - createResp.JSON201.Id, - adminapi.ChangeUserPasswordJSONRequestBody{OldPassword: "wrong-old-pass", NewPassword: "newpass"}, + userID, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(changeResp.StatusCode(), http.StatusBadRequest, t) - lib.VerifyAdminAPIErrorResponse(changeResp.JSON400, t) + lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) + if getResp.JSON200.Groups == nil { + t.Fatal("expected non-nil groups on user") + } + if len(*getResp.JSON200.Groups) != 2 { + t.Fatalf("expected 2 groups after update, got %d", len(*getResp.JSON200.Groups)) + } + groupIDs := make([]int, 0, len(*getResp.JSON200.Groups)) + for _, g := range *getResp.JSON200.Groups { + groupIDs = append(groupIDs, g.Id) + } + lib.ContainsAll([]int{grp2ID, grp3ID}, groupIDs, t) + for _, id := range groupIDs { + if id == grp1ID { + t.Fatalf("grp1 should have been removed from user groups") + } + } } -// TestAdminMeNormalUser verifies that a normal admin user receives their own user info from /me. -func TestAdminMeNormalUser(t *testing.T) { +// TestAdminUserGroupBindingsClear verifies that a user's group memberships can be cleared. +func TestAdminUserGroupBindingsClear(t *testing.T) { t.Parallel() superRequestEditor := lib.SuperLogin(t) name := lib.Username() - const pass = "mepassword123" - createResp, err := lib.AdminClient.CreateUserWithResponse( + createUserResp, err := lib.AdminClient.CreateUserWithResponse( t.Context(), - adminapi.CreateUserJSONRequestBody{Username: name, Password: pass}, + adminapi.CreateUserJSONRequestBody{Username: name, Password: "password123"}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(createResp.StatusCode(), http.StatusCreated, t) - - userRequestEditor := lib.AdminUserLogin(t, name, pass) + lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) + userID := lib.MustGetAdminUserID(t, superRequestEditor, name) - meResp, err := lib.AdminClient.GetMeWithResponse( + grpResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.RequestEditorFn(userRequestEditor), + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(meResp.StatusCode(), http.StatusOK, t) - - if meResp.JSON200 == nil { - t.Fatal("Expected non-nil JSON200 body") - } - if meResp.JSON200.IsSuperuser { - t.Fatal("Expected isSuperuser=false for normal admin user") - } - if meResp.JSON200.User == nil { - t.Fatal("Expected non-nil user field for normal admin user") - } - lib.Matches(meResp.JSON200.User.Username, name, t) - lib.Matches(meResp.JSON200.User.Id, createResp.JSON201.Id, t) -} + lib.VerifyStatusCode(grpResp.StatusCode(), http.StatusCreated, t) + grpID := grpResp.JSON201.Id -// TestAdminMeSuperuser verifies that the superuser receives isSuperuser=true and no user field from /me. -func TestAdminMeSuperuser(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) + // Assign to the group. + updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( + t.Context(), + userID, + adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{grpID}}, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - meResp, err := lib.AdminClient.GetMeWithResponse( + // Clear all groups. + updateResp, err = lib.AdminClient.UpdateUserGroupsWithResponse( t.Context(), + userID, + adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{}}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) - lib.VerifyStatusCode(meResp.StatusCode(), http.StatusOK, t) + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNoContent, t) - if meResp.JSON200 == nil { - t.Fatal("Expected non-nil JSON200 body") - } - if !meResp.JSON200.IsSuperuser { - t.Fatal("Expected isSuperuser=true for superuser") - } - if meResp.JSON200.User != nil { - t.Fatal("Expected nil user field for superuser") + getResp, err := lib.AdminClient.GetUserWithResponse( + t.Context(), + userID, + adminapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) + if getResp.JSON200.Groups != nil && len(*getResp.JSON200.Groups) != 0 { + t.Fatalf("expected 0 groups after clear, got %d", len(*getResp.JSON200.Groups)) } } -// TestAdminMeUnauthenticated verifies that calling /me without a session returns 401. -func TestAdminMeUnauthenticated(t *testing.T) { +// TestAdminUserGroupBindingsNotFoundUser verifies that updating groups for a non-existent user returns 404. +func TestAdminUserGroupBindingsNotFoundUser(t *testing.T) { t.Parallel() - meResp, err := lib.AdminClient.GetMeWithResponse(t.Context()) + superRequestEditor := lib.SuperLogin(t) + + updateResp, err := lib.AdminClient.UpdateUserGroupsWithResponse( + t.Context(), + 999999999, + adminapi.UpdateUserGroupsJSONRequestBody{GroupIDs: []int{}}, + adminapi.RequestEditorFn(superRequestEditor), + ) lib.CheckErr(err, t) - lib.VerifyStatusCode(meResp.StatusCode(), http.StatusUnauthorized, t) + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNotFound, t) + lib.VerifyAdminAPIErrorResponse(updateResp.JSON404, t) } diff --git a/test/suites/integration/auth_basic_api_test.go b/test/suites/integration/auth_basic_api_access_control_test.go similarity index 87% rename from test/suites/integration/auth_basic_api_test.go rename to test/suites/integration/auth_basic_api_access_control_test.go index 1419a9c..db798ec 100644 --- a/test/suites/integration/auth_basic_api_test.go +++ b/test/suites/integration/auth_basic_api_access_control_test.go @@ -8,9 +8,9 @@ import ( authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -// TestAuthBasicAPIOrganisationIsolation verifies that a session from one organisation +// TestBasicAuthOrganisationIsolation verifies that a session from one organisation // cannot read or mutate any resource that belongs to a different organisation. -func TestAuthBasicAPIOrganisationIsolation(t *testing.T) { +func TestBasicAuthOrganisationIsolation(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrg1, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -211,10 +211,10 @@ func TestAuthBasicAPIOrganisationIsolation(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(changePasswordCrossResp.JSON403, t) } -// TestAuthBasicAPIOrgAdminListOrganisationsForbidden verifies that a session scoped to an +// TestBasicAuthOrgAdminListOrganisationsForbidden verifies that a session scoped to an // organisation cannot list organisations (superuser-only operation). // The spec does not define a 403 body for ListOrganisations, so only the status is checked. -func TestAuthBasicAPIOrgAdminListOrganisationsForbidden(t *testing.T) { +func TestBasicAuthOrgAdminListOrganisationsForbidden(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -255,10 +255,10 @@ func TestAuthBasicAPIOrgAdminListOrganisationsForbidden(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(createResp.JSON403, t) } -// TestAuthBasicAPINormalUserAccessControl verifies that a non-administrator user receives +// TestBasicAuthNormalUserAccessControl verifies that a non-administrator user receives // 403 (with populated error body) for all admin-only operations, and can still successfully // retrieve their own user record. -func TestAuthBasicAPINormalUserAccessControl(t *testing.T) { +func TestBasicAuthNormalUserAccessControl(t *testing.T) { superRequestEditor := lib.SuperLogin(t) // Create a dedicated org for this test. @@ -443,56 +443,3 @@ func TestAuthBasicAPINormalUserAccessControl(t *testing.T) { lib.VerifyStatusCode(getUserSelfResp.StatusCode(), http.StatusOK, t) lib.Matches(getUserSelfResp.JSON200.Id, regularUserID, t) } - -// TestAuthBasicRefreshNoRefreshCookie verifies that calling the refresh endpoint without a -// refresh cookie returns 401. A missing session cookie alone does not cause a 401 — only the -// missing refresh cookie matters here. -func TestAuthBasicRefreshNoRefreshCookie(t *testing.T) { - t.Parallel() - resp, err := lib.BasicAuthClient.RefreshWithResponse( - t.Context(), - authbasicapi.Orgid(alwaysOrgID), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(resp.StatusCode(), http.StatusUnauthorized, t) - lib.VerifyAuthBasicAPIErrorResponse(resp.JSON401, t) -} - -// TestAuthBasicRefresh verifies that the refresh endpoint issues a new session when called -// with only the refresh cookie (no session cookie required). -func TestAuthBasicRefresh(t *testing.T) { - t.Parallel() - superRequestEditor := lib.SuperLogin(t) - - createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( - t.Context(), - authbasicapi.CreateOrganisationJSONRequestBody{Name: lib.OrgName()}, - authbasicapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createOrgResp.StatusCode(), http.StatusCreated, t) - - orgID := createOrgResp.JSON201.Id - - loginResp, err := lib.BasicAuthClient.LoginWithResponse( - t.Context(), - orgID, - authbasicapi.LoginJSONRequestBody{ - Username: createOrgResp.JSON201.AdminUsername, - Password: createOrgResp.JSON201.AdminPassword, - }, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) - - // Use only the refresh cookie — deliberately omit the session cookie to prove it is not required. - refreshEditor := lib.RefreshCookieRequestEditor(loginResp.HTTPResponse, t) - - refreshResp, err := lib.BasicAuthClient.RefreshWithResponse( - t.Context(), - orgID, - authbasicapi.RequestEditorFn(refreshEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(refreshResp.StatusCode(), http.StatusNoContent, t) -} diff --git a/test/suites/integration/auth_basic_api_bindings_test.go b/test/suites/integration/auth_basic_api_bindings_test.go deleted file mode 100644 index 4ee059d..0000000 --- a/test/suites/integration/auth_basic_api_bindings_test.go +++ /dev/null @@ -1,268 +0,0 @@ -package integration - -import ( - lib "github.com/trebent/kerberos/test/lib" - "net/http" - "slices" - "testing" - - adminapi "github.com/trebent/kerberos/test/client/admin" - authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" -) - -// TestUserGroupBindingAssign verifies that groups can be assigned to a user and are returned -// by GetUserGroups. -func TestUserGroupBindingAssign(t *testing.T) { - superLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse( - t.Context(), - adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: lib.SuperUserClientSecret}, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(superLoginResp.StatusCode(), http.StatusNoContent, t) - superRequestEditor := lib.SessionCookieRequestEditor(superLoginResp.HTTPResponse, t) - - orgID, adminRequestEditor := lib.OrgWithSession(t, superRequestEditor) - - groupAName := lib.GroupName() - createGroupA, err := lib.BasicAuthClient.CreateGroupWithResponse( - t.Context(), - orgID, - authbasicapi.CreateGroupJSONRequestBody{Name: groupAName}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createGroupA.StatusCode(), http.StatusCreated, t) - - groupBName := lib.GroupName() - createGroupB, err := lib.BasicAuthClient.CreateGroupWithResponse( - t.Context(), - orgID, - authbasicapi.CreateGroupJSONRequestBody{Name: groupBName}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createGroupB.StatusCode(), http.StatusCreated, t) - - createUserResp, err := lib.BasicAuthClient.CreateUserWithResponse( - t.Context(), - orgID, - authbasicapi.CreateUserJSONRequestBody{Name: lib.Username(), Password: "password123"}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) - userID := createUserResp.JSON201.Id - - updateResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( - t.Context(), - orgID, - userID, - authbasicapi.UpdateUserGroupsJSONRequestBody{ - {Id: createGroupA.JSON201.Id, Name: groupAName}, - {Id: createGroupB.JSON201.Id, Name: groupBName}, - }, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusOK, t) - - getResp, err := lib.BasicAuthClient.GetUserGroupsWithResponse( - t.Context(), - orgID, - userID, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) - groups := *getResp.JSON200 - if !slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == groupAName }) { - t.Fatalf("expected group %q in user groups, got %v", groupAName, groups) - } - if !slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == groupBName }) { - t.Fatalf("expected group %q in user groups, got %v", groupBName, groups) - } -} - -// TestUserGroupBindingReplace verifies that updating a user's groups replaces the previous -// set entirely — groups removed from the request are no longer returned. -func TestUserGroupBindingReplace(t *testing.T) { - superLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse( - t.Context(), - adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: lib.SuperUserClientSecret}, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(superLoginResp.StatusCode(), http.StatusNoContent, t) - superRequestEditor := lib.SessionCookieRequestEditor(superLoginResp.HTTPResponse, t) - - orgID, adminRequestEditor := lib.OrgWithSession(t, superRequestEditor) - - groupAName := lib.GroupName() - createGroupA, err := lib.BasicAuthClient.CreateGroupWithResponse( - t.Context(), - orgID, - authbasicapi.CreateGroupJSONRequestBody{Name: groupAName}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createGroupA.StatusCode(), http.StatusCreated, t) - - groupBName := lib.GroupName() - createGroupB, err := lib.BasicAuthClient.CreateGroupWithResponse( - t.Context(), - orgID, - authbasicapi.CreateGroupJSONRequestBody{Name: groupBName}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createGroupB.StatusCode(), http.StatusCreated, t) - - createUserResp, err := lib.BasicAuthClient.CreateUserWithResponse( - t.Context(), - orgID, - authbasicapi.CreateUserJSONRequestBody{Name: lib.Username(), Password: "password123"}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) - userID := createUserResp.JSON201.Id - - // Assign both groups initially. - initialUpdateResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( - t.Context(), - orgID, - userID, - authbasicapi.UpdateUserGroupsJSONRequestBody{ - {Id: createGroupA.JSON201.Id, Name: groupAName}, - {Id: createGroupB.JSON201.Id, Name: groupBName}, - }, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(initialUpdateResp.StatusCode(), http.StatusOK, t) - - // Replace with only group B — group A should be removed. - replaceResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( - t.Context(), - orgID, - userID, - authbasicapi.UpdateUserGroupsJSONRequestBody{ - {Id: createGroupB.JSON201.Id, Name: groupBName}, - }, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(replaceResp.StatusCode(), http.StatusOK, t) - - getResp, err := lib.BasicAuthClient.GetUserGroupsWithResponse( - t.Context(), - orgID, - userID, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) - groups := *getResp.JSON200 - if slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == groupAName }) { - t.Fatalf("group %q should have been removed after replace, got %v", groupAName, groups) - } - if !slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == groupBName }) { - t.Fatalf("expected group %q to remain after replace, got %v", groupBName, groups) - } -} - -// TestUserGroupBindingClear verifies that assigning an empty group list removes all group -// memberships from the user. -func TestUserGroupBindingClear(t *testing.T) { - superLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse( - t.Context(), - adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: lib.SuperUserClientSecret}, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(superLoginResp.StatusCode(), http.StatusNoContent, t) - superRequestEditor := lib.SessionCookieRequestEditor(superLoginResp.HTTPResponse, t) - - orgID, adminRequestEditor := lib.OrgWithSession(t, superRequestEditor) - - gName := lib.GroupName() - createGroupResp, err := lib.BasicAuthClient.CreateGroupWithResponse( - t.Context(), - orgID, - authbasicapi.CreateGroupJSONRequestBody{Name: gName}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createGroupResp.StatusCode(), http.StatusCreated, t) - - createUserResp, err := lib.BasicAuthClient.CreateUserWithResponse( - t.Context(), - orgID, - authbasicapi.CreateUserJSONRequestBody{Name: lib.Username(), Password: "password123"}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) - userID := createUserResp.JSON201.Id - - // Assign the group first. - assignResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( - t.Context(), - orgID, - userID, - authbasicapi.UpdateUserGroupsJSONRequestBody{ - {Id: createGroupResp.JSON201.Id, Name: gName}, - }, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(assignResp.StatusCode(), http.StatusOK, t) - - // Clear all groups. - clearResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( - t.Context(), - orgID, - userID, - authbasicapi.UpdateUserGroupsJSONRequestBody{}, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(clearResp.StatusCode(), http.StatusOK, t) - - getResp, err := lib.BasicAuthClient.GetUserGroupsWithResponse( - t.Context(), - orgID, - userID, - authbasicapi.RequestEditorFn(adminRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) - if len(*getResp.JSON200) != 0 { - t.Fatalf("expected empty groups after clear, got %v", *getResp.JSON200) - } -} - -// TestUserGroupBindingGet verifies that GetUserGroups returns the expected groups for a user -// that was set up with known group memberships in TestMain. -func TestUserGroupBindingGet(t *testing.T) { - loginResp, err := lib.AdminClient.LoginSuperuserWithResponse( - t.Context(), - adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: lib.SuperUserClientSecret}, - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) - superRequestEditor := lib.SessionCookieRequestEditor(loginResp.HTTPResponse, t) - - getResp, err := lib.BasicAuthClient.GetUserGroupsWithResponse( - t.Context(), - authbasicapi.Orgid(alwaysOrgID), - authbasicapi.Userid(alwaysUserID), - authbasicapi.RequestEditorFn(superRequestEditor), - ) - lib.CheckErr(err, t) - lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) - groups := *getResp.JSON200 - for _, expected := range []string{alwaysGroupStaff, alwaysGroupPleb, alwaysGroupDev} { - if !slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == expected }) { - t.Fatalf("expected group %q in always-user groups, got %v", expected, groups) - } - } -} diff --git a/test/suites/integration/auth_basic_api_groups_test.go b/test/suites/integration/auth_basic_api_groups_test.go index b753693..79bdf41 100644 --- a/test/suites/integration/auth_basic_api_groups_test.go +++ b/test/suites/integration/auth_basic_api_groups_test.go @@ -8,9 +8,9 @@ import ( authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -// TestGroupCreate verifies that a new group can be created within an organisation and that +// TestBasicAuthGroupCreate verifies that a new group can be created within an organisation and that // the response contains the expected name and a valid ID. -func TestGroupCreate(t *testing.T) { +func TestBasicAuthGroupCreate(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.GroupName() @@ -28,8 +28,8 @@ func TestGroupCreate(t *testing.T) { } } -// TestGroupList verifies that a newly created group appears in the list response for its organisation. -func TestGroupList(t *testing.T) { +// TestBasicAuthGroupList verifies that a newly created group appears in the list response for its organisation. +func TestBasicAuthGroupList(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateGroupWithResponse( @@ -57,8 +57,8 @@ func TestGroupList(t *testing.T) { t.Fatalf("created group %d not found in list response", createdID) } -// TestGroupGet verifies that a created group can be fetched by ID. -func TestGroupGet(t *testing.T) { +// TestBasicAuthGroupGet verifies that a created group can be fetched by ID. +func TestBasicAuthGroupGet(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.GroupName() @@ -83,8 +83,8 @@ func TestGroupGet(t *testing.T) { lib.Matches(getResp.JSON200.Name, name, t) } -// TestGroupGetNotFound verifies that fetching a deleted group returns 404. -func TestGroupGetNotFound(t *testing.T) { +// TestBasicAuthGroupGetNotFound verifies that fetching a deleted group returns 404. +func TestBasicAuthGroupGetNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -125,9 +125,9 @@ func TestGroupGetNotFound(t *testing.T) { lib.VerifyStatusCode(getResp.StatusCode(), http.StatusNotFound, t) } -// TestGroupUpdate verifies that a group's name can be changed and the updated value is +// TestBasicAuthGroupUpdate verifies that a group's name can be changed and the updated value is // reflected in a subsequent get. -func TestGroupUpdate(t *testing.T) { +func TestBasicAuthGroupUpdate(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateGroupWithResponse( @@ -163,9 +163,9 @@ func TestGroupUpdate(t *testing.T) { lib.Matches(getResp.JSON200.Name, newName, t) } -// TestGroupUpdateConflict verifies that renaming a group to an already-taken name within the +// TestBasicAuthGroupUpdateConflict verifies that renaming a group to an already-taken name within the // same organisation returns a conflict error. -func TestGroupUpdateConflict(t *testing.T) { +func TestBasicAuthGroupUpdateConflict(t *testing.T) { superRequestEditor := lib.SuperLogin(t) create1Resp, err := lib.BasicAuthClient.CreateGroupWithResponse( @@ -198,9 +198,9 @@ func TestGroupUpdateConflict(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(updateResp.JSON409, t) } -// TestGroupCreateConflict verifies that creating a group whose name already exists within the +// TestBasicAuthGroupCreateConflict verifies that creating a group whose name already exists within the // same organisation returns a conflict error. -func TestGroupCreateConflict(t *testing.T) { +func TestBasicAuthGroupCreateConflict(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.GroupName() @@ -224,8 +224,8 @@ func TestGroupCreateConflict(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(conflictResp.JSON409, t) } -// TestGroupDelete verifies that a deleted group is no longer accessible. -func TestGroupDelete(t *testing.T) { +// TestBasicAuthGroupDelete verifies that a deleted group is no longer accessible. +func TestBasicAuthGroupDelete(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -266,9 +266,9 @@ func TestGroupDelete(t *testing.T) { lib.VerifyStatusCode(getResp.StatusCode(), http.StatusNotFound, t) } -// TestGroupCreateOASValidation verifies that creating a group with an empty name is +// TestBasicAuthGroupCreateOASValidation verifies that creating a group with an empty name is // rejected with 400 by the OAS validator (name has minLength: 1). -func TestGroupCreateOASValidation(t *testing.T) { +func TestBasicAuthGroupCreateOASValidation(t *testing.T) { superRequestEditor := lib.SuperLogin(t) // Name below minLength: 1 — must be rejected. @@ -283,9 +283,9 @@ func TestGroupCreateOASValidation(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(createResp.JSON400, t) } -// TestGroupUpdateOASValidation verifies that updating a group with an empty name is +// TestBasicAuthGroupUpdateOASValidation verifies that updating a group with an empty name is // rejected with 400 by the OAS validator (Group.name has minLength: 1). -func TestGroupUpdateOASValidation(t *testing.T) { +func TestBasicAuthGroupUpdateOASValidation(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateGroupWithResponse( @@ -310,9 +310,9 @@ func TestGroupUpdateOASValidation(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(updateResp.JSON400, t) } -// TestGroupNoSession verifies that every group-scoped endpoint returns 401 with a +// TestBasicAuthGroupNoSession verifies that every group-scoped endpoint returns 401 with a // populated error body when called without a session header. -func TestGroupNoSession(t *testing.T) { +func TestBasicAuthGroupNoSession(t *testing.T) { // CreateGroup — no session. createResp, err := lib.BasicAuthClient.CreateGroupWithResponse( t.Context(), @@ -364,8 +364,8 @@ func TestGroupNoSession(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(deleteResp.JSON401, t) } -// TestGroupDeleteNotFound verifies deleting an already-deleted group. -func TestGroupDeleteNotFound(t *testing.T) { +// TestBasicAuthGroupDeleteNotFound verifies deleting an already-deleted group. +func TestBasicAuthGroupDeleteNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -408,9 +408,9 @@ func TestGroupDeleteNotFound(t *testing.T) { lib.VerifyStatusCode(deleteAgainResp.StatusCode(), http.StatusNoContent, t) } -// TestGroupUpdateNotFound verifies that attempting to update a deleted group returns 404 +// TestBasicAuthGroupUpdateNotFound verifies that attempting to update a deleted group returns 404 // (no body defined in spec). -func TestGroupUpdateNotFound(t *testing.T) { +func TestBasicAuthGroupUpdateNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( diff --git a/test/suites/integration/auth_basic_api_organisations_test.go b/test/suites/integration/auth_basic_api_organisations_test.go index fe1a787..2042638 100644 --- a/test/suites/integration/auth_basic_api_organisations_test.go +++ b/test/suites/integration/auth_basic_api_organisations_test.go @@ -8,9 +8,9 @@ import ( authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -// TestOrganisationCreate verifies that a superuser can create an organisation and +// TestBasicAuthOrganisationCreate verifies that a superuser can create an organisation and // that the response includes the generated admin credentials. -func TestOrganisationCreate(t *testing.T) { +func TestBasicAuthOrganisationCreate(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.OrgName() @@ -33,8 +33,8 @@ func TestOrganisationCreate(t *testing.T) { } } -// TestOrganisationList verifies that a newly created organisation appears in the list response. -func TestOrganisationList(t *testing.T) { +// TestBasicAuthOrganisationList verifies that a newly created organisation appears in the list response. +func TestBasicAuthOrganisationList(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -60,8 +60,8 @@ func TestOrganisationList(t *testing.T) { t.Fatalf("created organisation %d not found in list response", createdID) } -// TestOrganisationGet verifies that a created organisation can be fetched by ID. -func TestOrganisationGet(t *testing.T) { +// TestBasicAuthOrganisationGet verifies that a created organisation can be fetched by ID. +func TestBasicAuthOrganisationGet(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.OrgName() @@ -84,8 +84,8 @@ func TestOrganisationGet(t *testing.T) { lib.Matches(getResp.JSON200.Name, name, t) } -// TestOrganisationGetNotFound verifies that fetching a deleted organisation returns 404. -func TestOrganisationGetNotFound(t *testing.T) { +// TestBasicAuthOrganisationGetNotFound verifies that fetching a deleted organisation returns 404. +func TestBasicAuthOrganisationGetNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -114,9 +114,9 @@ func TestOrganisationGetNotFound(t *testing.T) { lib.VerifyStatusCode(getResp.StatusCode(), http.StatusNotFound, t) } -// TestOrganisationUpdate verifies that an organisation's name can be changed and the +// TestBasicAuthOrganisationUpdate verifies that an organisation's name can be changed and the // updated value is reflected in a subsequent get. -func TestOrganisationUpdate(t *testing.T) { +func TestBasicAuthOrganisationUpdate(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -149,9 +149,9 @@ func TestOrganisationUpdate(t *testing.T) { lib.Matches(getResp.JSON200.Name, newName, t) } -// TestOrganisationUpdateConflict verifies that renaming an organisation to an already-taken +// TestBasicAuthOrganisationUpdateConflict verifies that renaming an organisation to an already-taken // name returns a conflict error. -func TestOrganisationUpdateConflict(t *testing.T) { +func TestBasicAuthOrganisationUpdateConflict(t *testing.T) { superRequestEditor := lib.SuperLogin(t) create1Resp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -181,9 +181,9 @@ func TestOrganisationUpdateConflict(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(updateResp.JSON409, t) } -// TestOrganisationCreateConflict verifies that creating an organisation whose name is already +// TestBasicAuthOrganisationCreateConflict verifies that creating an organisation whose name is already // taken returns a conflict error. -func TestOrganisationCreateConflict(t *testing.T) { +func TestBasicAuthOrganisationCreateConflict(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.OrgName() @@ -205,8 +205,8 @@ func TestOrganisationCreateConflict(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(conflictResp.JSON409, t) } -// TestOrganisationDelete verifies that a deleted organisation is no longer accessible. -func TestOrganisationDelete(t *testing.T) { +// TestBasicAuthOrganisationDelete verifies that a deleted organisation is no longer accessible. +func TestBasicAuthOrganisationDelete(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -227,9 +227,9 @@ func TestOrganisationDelete(t *testing.T) { lib.VerifyStatusCode(deleteResp.StatusCode(), http.StatusNoContent, t) } -// TestOrganisationCreateDenied verifies that an organisation-scoped session cannot create +// TestBasicAuthOrganisationCreateDenied verifies that an organisation-scoped session cannot create // new organisations. -func TestOrganisationCreateDenied(t *testing.T) { +func TestBasicAuthOrganisationCreateDenied(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -261,9 +261,9 @@ func TestOrganisationCreateDenied(t *testing.T) { lib.VerifyStatusCode(denyResp.StatusCode(), http.StatusForbidden, t) } -// TestOrganisationCreateOASValidation verifies that creating an organisation with an empty +// TestBasicAuthOrganisationCreateOASValidation verifies that creating an organisation with an empty // name is rejected with 400 by the OAS validator. -func TestOrganisationCreateOASValidation(t *testing.T) { +func TestBasicAuthOrganisationCreateOASValidation(t *testing.T) { superRequestEditor := lib.SuperLogin(t) // Name below minLength: 1 — must be rejected. @@ -277,9 +277,9 @@ func TestOrganisationCreateOASValidation(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(createResp.JSON400, t) } -// TestOrganisationLogin verifies that a user can log in to their organisation and receives +// TestBasicAuthOrganisationLogin verifies that a user can log in to their organisation and receives // a session token in the response header. -func TestOrganisationLogin(t *testing.T) { +func TestBasicAuthOrganisationLogin(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -303,9 +303,9 @@ func TestOrganisationLogin(t *testing.T) { _ = lib.SessionCookieRequestEditor(loginResp.HTTPResponse, t) } -// TestOrganisationLoginInvalidCredentials verifies that a login attempt with the wrong +// TestBasicAuthOrganisationLoginInvalidCredentials verifies that a login attempt with the wrong // password returns 401. -func TestOrganisationLoginInvalidCredentials(t *testing.T) { +func TestBasicAuthOrganisationLoginInvalidCredentials(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -329,9 +329,9 @@ func TestOrganisationLoginInvalidCredentials(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(loginResp.JSON401, t) } -// TestOrganisationLogout verifies that logging out invalidates the session token so that +// TestBasicAuthOrganisationLogout verifies that logging out invalidates the session token so that // subsequent authenticated requests are rejected with 401. -func TestOrganisationLogout(t *testing.T) { +func TestBasicAuthOrganisationLogout(t *testing.T) { loginResp, err := lib.BasicAuthClient.LoginWithResponse( t.Context(), authbasicapi.Orgid(alwaysOrgID), @@ -374,9 +374,9 @@ func TestOrganisationLogout(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(getUserAfterLogoutResp.JSON401, t) } -// TestOrganisationNoSession verifies that every organisation-scoped endpoint returns 401 +// TestBasicAuthOrganisationNoSession verifies that every organisation-scoped endpoint returns 401 // with a populated error body when called without a session header. -func TestOrganisationNoSession(t *testing.T) { +func TestBasicAuthOrganisationNoSession(t *testing.T) { // CreateOrganisation — no session. createResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( t.Context(), @@ -430,10 +430,10 @@ func TestOrganisationNoSession(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(logoutResp.JSON401, t) } -// TestOrganisationCrossOrgForbidden verifies that GetOrganisation and DeleteOrganisation +// TestBasicAuthOrganisationCrossOrgForbidden verifies that GetOrganisation and DeleteOrganisation // return 403 with a populated error body when called with a session from a different // organisation. -func TestOrganisationCrossOrgForbidden(t *testing.T) { +func TestBasicAuthOrganisationCrossOrgForbidden(t *testing.T) { superRequestEditor := lib.SuperLogin(t) // Create two organisations; each login produces a session scoped to that org. @@ -486,8 +486,8 @@ func TestOrganisationCrossOrgForbidden(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(deleteOrg1Resp.JSON403, t) } -// TestOrganisationDeleteNotFound verifies deleting an already-deleted organisation. -func TestOrganisationDeleteNotFound(t *testing.T) { +// TestBasicAuthOrganisationDeleteNotFound verifies deleting an already-deleted organisation. +func TestBasicAuthOrganisationDeleteNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -517,3 +517,56 @@ func TestOrganisationDeleteNotFound(t *testing.T) { lib.CheckErr(err, t) lib.VerifyStatusCode(deleteAgainResp.StatusCode(), http.StatusNoContent, t) } + +// TestBasicAuthOrganisationRefreshNoRefreshCookie verifies that calling the refresh endpoint without a +// refresh cookie returns 401. A missing session cookie alone does not cause a 401 — only the +// missing refresh cookie matters here. +func TestBasicAuthOrganisationRefreshNoRefreshCookie(t *testing.T) { + t.Parallel() + resp, err := lib.BasicAuthClient.RefreshWithResponse( + t.Context(), + authbasicapi.Orgid(alwaysOrgID), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(resp.StatusCode(), http.StatusUnauthorized, t) + lib.VerifyAuthBasicAPIErrorResponse(resp.JSON401, t) +} + +// TestBasicAuthOrganisationRefresh verifies that the refresh endpoint issues a new session when called +// with only the refresh cookie (no session cookie required). +func TestBasicAuthOrganisationRefresh(t *testing.T) { + t.Parallel() + superRequestEditor := lib.SuperLogin(t) + + createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( + t.Context(), + authbasicapi.CreateOrganisationJSONRequestBody{Name: lib.OrgName()}, + authbasicapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createOrgResp.StatusCode(), http.StatusCreated, t) + + orgID := createOrgResp.JSON201.Id + + loginResp, err := lib.BasicAuthClient.LoginWithResponse( + t.Context(), + orgID, + authbasicapi.LoginJSONRequestBody{ + Username: createOrgResp.JSON201.AdminUsername, + Password: createOrgResp.JSON201.AdminPassword, + }, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) + + // Use only the refresh cookie — deliberately omit the session cookie to prove it is not required. + refreshEditor := lib.RefreshCookieRequestEditor(loginResp.HTTPResponse, t) + + refreshResp, err := lib.BasicAuthClient.RefreshWithResponse( + t.Context(), + orgID, + authbasicapi.RequestEditorFn(refreshEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(refreshResp.StatusCode(), http.StatusNoContent, t) +} diff --git a/test/suites/integration/auth_basic_api_users_test.go b/test/suites/integration/auth_basic_api_users_test.go index 21533a4..a0b6eae 100644 --- a/test/suites/integration/auth_basic_api_users_test.go +++ b/test/suites/integration/auth_basic_api_users_test.go @@ -3,14 +3,16 @@ package integration import ( lib "github.com/trebent/kerberos/test/lib" "net/http" + "slices" "testing" + adminapi "github.com/trebent/kerberos/test/client/admin" authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -// TestUserCreate verifies that a new user can be created within an organisation and that +// TestBasicAuthUserCreate verifies that a new user can be created within an organisation and that // the response contains the expected name and a valid ID. -func TestUserCreate(t *testing.T) { +func TestBasicAuthUserCreate(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.Username() @@ -28,8 +30,8 @@ func TestUserCreate(t *testing.T) { } } -// TestUserList verifies that a newly created user appears in the list response for its organisation. -func TestUserList(t *testing.T) { +// TestBasicAuthUserList verifies that a newly created user appears in the list response for its organisation. +func TestBasicAuthUserList(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateUserWithResponse( @@ -57,8 +59,8 @@ func TestUserList(t *testing.T) { t.Fatalf("created user %d not found in list response", createdID) } -// TestUserGet verifies that a created user can be fetched by ID. -func TestUserGet(t *testing.T) { +// TestBasicAuthUserGet verifies that a created user can be fetched by ID. +func TestBasicAuthUserGet(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.Username() @@ -83,8 +85,8 @@ func TestUserGet(t *testing.T) { lib.Matches(getResp.JSON200.Name, name, t) } -// TestUserGetNotFound verifies that fetching a deleted user returns 404. -func TestUserGetNotFound(t *testing.T) { +// TestBasicAuthUserGetNotFound verifies that fetching a deleted user returns 404. +func TestBasicAuthUserGetNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -125,9 +127,9 @@ func TestUserGetNotFound(t *testing.T) { lib.VerifyStatusCode(getResp.StatusCode(), http.StatusNotFound, t) } -// TestUserUpdate verifies that a user's name can be changed and the updated value is +// TestBasicAuthUserUpdate verifies that a user's name can be changed and the updated value is // reflected in a subsequent get. -func TestUserUpdate(t *testing.T) { +func TestBasicAuthUserUpdate(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createResp, err := lib.BasicAuthClient.CreateUserWithResponse( @@ -163,9 +165,9 @@ func TestUserUpdate(t *testing.T) { lib.Matches(getResp.JSON200.Name, newName, t) } -// TestUserUpdateConflict verifies that renaming a user to an already-taken name within the +// TestBasicAuthUserUpdateConflict verifies that renaming a user to an already-taken name within the // same organisation returns a conflict error. -func TestUserUpdateConflict(t *testing.T) { +func TestBasicAuthUserUpdateConflict(t *testing.T) { superRequestEditor := lib.SuperLogin(t) create1Resp, err := lib.BasicAuthClient.CreateUserWithResponse( @@ -198,9 +200,9 @@ func TestUserUpdateConflict(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(updateResp.JSON409, t) } -// TestUserCreateConflict verifies that creating a user whose name already exists within the +// TestBasicAuthUserCreateConflict verifies that creating a user whose name already exists within the // same organisation returns a conflict error. -func TestUserCreateConflict(t *testing.T) { +func TestBasicAuthUserCreateConflict(t *testing.T) { superRequestEditor := lib.SuperLogin(t) name := lib.Username() @@ -224,8 +226,8 @@ func TestUserCreateConflict(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(conflictResp.JSON409, t) } -// TestUserDelete verifies that a deleted user is no longer accessible. -func TestUserDelete(t *testing.T) { +// TestBasicAuthUserDelete verifies that a deleted user is no longer accessible. +func TestBasicAuthUserDelete(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -266,9 +268,9 @@ func TestUserDelete(t *testing.T) { lib.VerifyStatusCode(getResp.StatusCode(), http.StatusNotFound, t) } -// TestUserCreateOASValidation verifies that creating a user with a name that is too short +// TestBasicAuthUserCreateOASValidation verifies that creating a user with a name that is too short // or a password that is outside the allowed length range is rejected with 400. -func TestUserCreateOASValidation(t *testing.T) { +func TestBasicAuthUserCreateOASValidation(t *testing.T) { superRequestEditor := lib.SuperLogin(t) // Name below minLength: 5 — must be rejected. @@ -305,9 +307,9 @@ func TestUserCreateOASValidation(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(longPasswordResp.JSON400, t) } -// TestUserChangePassword verifies the full change-password flow: a user can log in, +// TestBasicAuthUserChangePassword verifies the full change-password flow: a user can log in, // change their password, and then log in again with the new password. -func TestUserChangePassword(t *testing.T) { +func TestBasicAuthUserChangePassword(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -372,11 +374,11 @@ func TestUserChangePassword(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(oldLoginResp.JSON401, t) } -// TestUserChangePasswordOASValidation verifies that the OAS validator rejects change-password +// TestBasicAuthUserChangePasswordOASValidation verifies that the OAS validator rejects change-password // requests with credentials that violate the schema length constraints. // Note: the spec does not define a 400 response body for this endpoint, so only the // status code is checked. -func TestUserChangePasswordOASValidation(t *testing.T) { +func TestBasicAuthUserChangePasswordOASValidation(t *testing.T) { superRequestEditor := lib.SuperLogin(t) // oldPassword below minLength: 10 — must be rejected before auth checks. @@ -402,9 +404,9 @@ func TestUserChangePasswordOASValidation(t *testing.T) { lib.VerifyStatusCode(shortNewPwResp.StatusCode(), http.StatusBadRequest, t) } -// TestUserNoSession verifies that every user-scoped endpoint returns 401 with a populated +// TestBasicAuthUserNoSession verifies that every user-scoped endpoint returns 401 with a populated // error body when called without a session header. -func TestUserNoSession(t *testing.T) { +func TestBasicAuthUserNoSession(t *testing.T) { // CreateUser — no session. createResp, err := lib.BasicAuthClient.CreateUserWithResponse( t.Context(), @@ -488,8 +490,8 @@ func TestUserNoSession(t *testing.T) { lib.VerifyAuthBasicAPIErrorResponse(changePwResp.JSON401, t) } -// TestUserDeleteNotFound verifies deleting an already-deleted user. -func TestUserDeleteNotFound(t *testing.T) { +// TestBasicAuthUserDeleteNotFound verifies deleting an already-deleted user. +func TestBasicAuthUserDeleteNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -532,9 +534,9 @@ func TestUserDeleteNotFound(t *testing.T) { lib.VerifyStatusCode(deleteAgainResp.StatusCode(), http.StatusNoContent, t) } -// TestUserUpdateNotFound verifies that attempting to update a deleted user returns 404 +// TestBasicAuthUserUpdateNotFound verifies that attempting to update a deleted user returns 404 // (no body defined in spec). -func TestUserUpdateNotFound(t *testing.T) { +func TestBasicAuthUserUpdateNotFound(t *testing.T) { superRequestEditor := lib.SuperLogin(t) createOrgResp, err := lib.BasicAuthClient.CreateOrganisationWithResponse( @@ -577,3 +579,260 @@ func TestUserUpdateNotFound(t *testing.T) { lib.CheckErr(err, t) lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusNotFound, t) } + +// TestBasicAuthUserGroupBindingAssign verifies that groups can be assigned to a user and are returned +// by GetUserGroups. +func TestBasicAuthUserGroupBindingAssign(t *testing.T) { + superLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse( + t.Context(), + adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: lib.SuperUserClientSecret}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(superLoginResp.StatusCode(), http.StatusNoContent, t) + superRequestEditor := lib.SessionCookieRequestEditor(superLoginResp.HTTPResponse, t) + + orgID, adminRequestEditor := lib.OrgWithSession(t, superRequestEditor) + + groupAName := lib.GroupName() + createGroupA, err := lib.BasicAuthClient.CreateGroupWithResponse( + t.Context(), + orgID, + authbasicapi.CreateGroupJSONRequestBody{Name: groupAName}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createGroupA.StatusCode(), http.StatusCreated, t) + + groupBName := lib.GroupName() + createGroupB, err := lib.BasicAuthClient.CreateGroupWithResponse( + t.Context(), + orgID, + authbasicapi.CreateGroupJSONRequestBody{Name: groupBName}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createGroupB.StatusCode(), http.StatusCreated, t) + + createUserResp, err := lib.BasicAuthClient.CreateUserWithResponse( + t.Context(), + orgID, + authbasicapi.CreateUserJSONRequestBody{Name: lib.Username(), Password: "password123"}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) + userID := createUserResp.JSON201.Id + + updateResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( + t.Context(), + orgID, + userID, + authbasicapi.UpdateUserGroupsJSONRequestBody{ + {Id: createGroupA.JSON201.Id, Name: groupAName}, + {Id: createGroupB.JSON201.Id, Name: groupBName}, + }, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(updateResp.StatusCode(), http.StatusOK, t) + + getResp, err := lib.BasicAuthClient.GetUserGroupsWithResponse( + t.Context(), + orgID, + userID, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) + groups := *getResp.JSON200 + if !slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == groupAName }) { + t.Fatalf("expected group %q in user groups, got %v", groupAName, groups) + } + if !slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == groupBName }) { + t.Fatalf("expected group %q in user groups, got %v", groupBName, groups) + } +} + +// TestBasicAuthUserGroupBindingReplace verifies that updating a user's groups replaces the previous +// set entirely — groups removed from the request are no longer returned. +func TestBasicAuthUserGroupBindingReplace(t *testing.T) { + superLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse( + t.Context(), + adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: lib.SuperUserClientSecret}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(superLoginResp.StatusCode(), http.StatusNoContent, t) + superRequestEditor := lib.SessionCookieRequestEditor(superLoginResp.HTTPResponse, t) + + orgID, adminRequestEditor := lib.OrgWithSession(t, superRequestEditor) + + groupAName := lib.GroupName() + createGroupA, err := lib.BasicAuthClient.CreateGroupWithResponse( + t.Context(), + orgID, + authbasicapi.CreateGroupJSONRequestBody{Name: groupAName}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createGroupA.StatusCode(), http.StatusCreated, t) + + groupBName := lib.GroupName() + createGroupB, err := lib.BasicAuthClient.CreateGroupWithResponse( + t.Context(), + orgID, + authbasicapi.CreateGroupJSONRequestBody{Name: groupBName}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createGroupB.StatusCode(), http.StatusCreated, t) + + createUserResp, err := lib.BasicAuthClient.CreateUserWithResponse( + t.Context(), + orgID, + authbasicapi.CreateUserJSONRequestBody{Name: lib.Username(), Password: "password123"}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) + userID := createUserResp.JSON201.Id + + // Assign both groups initially. + initialUpdateResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( + t.Context(), + orgID, + userID, + authbasicapi.UpdateUserGroupsJSONRequestBody{ + {Id: createGroupA.JSON201.Id, Name: groupAName}, + {Id: createGroupB.JSON201.Id, Name: groupBName}, + }, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(initialUpdateResp.StatusCode(), http.StatusOK, t) + + // Replace with only group B — group A should be removed. + replaceResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( + t.Context(), + orgID, + userID, + authbasicapi.UpdateUserGroupsJSONRequestBody{ + {Id: createGroupB.JSON201.Id, Name: groupBName}, + }, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(replaceResp.StatusCode(), http.StatusOK, t) + + getResp, err := lib.BasicAuthClient.GetUserGroupsWithResponse( + t.Context(), + orgID, + userID, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) + groups := *getResp.JSON200 + if slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == groupAName }) { + t.Fatalf("group %q should have been removed after replace, got %v", groupAName, groups) + } + if !slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == groupBName }) { + t.Fatalf("expected group %q to remain after replace, got %v", groupBName, groups) + } +} + +// TestBasicAuthUserGroupBindingClear verifies that assigning an empty group list removes all group +// memberships from the user. +func TestBasicAuthUserGroupBindingClear(t *testing.T) { + superLoginResp, err := lib.AdminClient.LoginSuperuserWithResponse( + t.Context(), + adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: lib.SuperUserClientSecret}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(superLoginResp.StatusCode(), http.StatusNoContent, t) + superRequestEditor := lib.SessionCookieRequestEditor(superLoginResp.HTTPResponse, t) + + orgID, adminRequestEditor := lib.OrgWithSession(t, superRequestEditor) + + gName := lib.GroupName() + createGroupResp, err := lib.BasicAuthClient.CreateGroupWithResponse( + t.Context(), + orgID, + authbasicapi.CreateGroupJSONRequestBody{Name: gName}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createGroupResp.StatusCode(), http.StatusCreated, t) + + createUserResp, err := lib.BasicAuthClient.CreateUserWithResponse( + t.Context(), + orgID, + authbasicapi.CreateUserJSONRequestBody{Name: lib.Username(), Password: "password123"}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(createUserResp.StatusCode(), http.StatusCreated, t) + userID := createUserResp.JSON201.Id + + // Assign the group first. + assignResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( + t.Context(), + orgID, + userID, + authbasicapi.UpdateUserGroupsJSONRequestBody{ + {Id: createGroupResp.JSON201.Id, Name: gName}, + }, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(assignResp.StatusCode(), http.StatusOK, t) + + // Clear all groups. + clearResp, err := lib.BasicAuthClient.UpdateUserGroupsWithResponse( + t.Context(), + orgID, + userID, + authbasicapi.UpdateUserGroupsJSONRequestBody{}, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(clearResp.StatusCode(), http.StatusOK, t) + + getResp, err := lib.BasicAuthClient.GetUserGroupsWithResponse( + t.Context(), + orgID, + userID, + authbasicapi.RequestEditorFn(adminRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) + if len(*getResp.JSON200) != 0 { + t.Fatalf("expected empty groups after clear, got %v", *getResp.JSON200) + } +} + +// TestBasicAuthUserGroupBindingGet verifies that GetUserGroups returns the expected groups for a user +// that was set up with known group memberships in TestMain. +func TestBasicAuthUserGroupBindingGet(t *testing.T) { + loginResp, err := lib.AdminClient.LoginSuperuserWithResponse( + t.Context(), + adminapi.LoginSuperuserJSONRequestBody{ClientId: lib.SuperUserClientID, ClientSecret: lib.SuperUserClientSecret}, + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(loginResp.StatusCode(), http.StatusNoContent, t) + superRequestEditor := lib.SessionCookieRequestEditor(loginResp.HTTPResponse, t) + + getResp, err := lib.BasicAuthClient.GetUserGroupsWithResponse( + t.Context(), + authbasicapi.Orgid(alwaysOrgID), + authbasicapi.Userid(alwaysUserID), + authbasicapi.RequestEditorFn(superRequestEditor), + ) + lib.CheckErr(err, t) + lib.VerifyStatusCode(getResp.StatusCode(), http.StatusOK, t) + groups := *getResp.JSON200 + for _, expected := range []string{alwaysGroupStaff, alwaysGroupPleb, alwaysGroupDev} { + if !slices.ContainsFunc(groups, func(g authbasicapi.Group) bool { return g.Name == expected }) { + t.Fatalf("expected group %q in always-user groups, got %v", expected, groups) + } + } +} diff --git a/test/suites/integration/cookies_test.go b/test/suites/integration/cookies_test.go index b5a6a35..ac2ac77 100644 --- a/test/suites/integration/cookies_test.go +++ b/test/suites/integration/cookies_test.go @@ -10,7 +10,7 @@ import ( authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -func TestCookies_admin(t *testing.T) { +func TestCookiesAdmin(t *testing.T) { t.Run("Verify superuser cookie attributes", func(t *testing.T) { t.Parallel() resp, err := lib.AdminClient.LoginSuperuser( @@ -42,7 +42,7 @@ func TestCookies_admin(t *testing.T) { }) } -func TestCookies_basicauth(t *testing.T) { +func TestCookiesBasicAuth(t *testing.T) { t.Run("Verify basic auth cookie attributes", func(t *testing.T) { t.Parallel() loginResp, err := lib.BasicAuthClient.Login( diff --git a/test/suites/integration/cors_test.go b/test/suites/integration/cors_test.go index c7e7786..96e4098 100644 --- a/test/suites/integration/cors_test.go +++ b/test/suites/integration/cors_test.go @@ -11,7 +11,7 @@ import ( authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -func TestCORS_admin(t *testing.T) { +func TestCORSAdmin(t *testing.T) { t.Run("OPTIONS preflight with Origin - CORS headers returned", func(t *testing.T) { t.Parallel() url := fmt.Sprintf("http://%s:%d/api/admin/login", lib.GetHost(), lib.GetAdminPort()) @@ -60,7 +60,7 @@ func TestCORS_admin(t *testing.T) { }) } -func TestCORS_basicauth(t *testing.T) { +func TestCORSBasicAuth(t *testing.T) { t.Run("OPTIONS preflight with Origin - CORS headers returned", func(t *testing.T) { t.Parallel() url := fmt.Sprintf("http://%s:%d/api/auth/basic/organisations/%d/login", lib.GetHost(), lib.GetAdminPort(), alwaysOrgID) @@ -107,7 +107,7 @@ func TestCORS_basicauth(t *testing.T) { }) } -func TestCORS_gateway(t *testing.T) { +func TestCORSGateway(t *testing.T) { baseURL := fmt.Sprintf("http://localhost:%d/gw/backend/echo", lib.GetPort()) // normal echo has allowAll, but since Origin is omitted, we should not see a returned CORS header. diff --git a/test/suites/integration/auth_basic_test.go b/test/suites/integration/gateway_auth_basic_test.go similarity index 94% rename from test/suites/integration/auth_basic_test.go rename to test/suites/integration/gateway_auth_basic_test.go index b229290..73b97bc 100644 --- a/test/suites/integration/auth_basic_test.go +++ b/test/suites/integration/gateway_auth_basic_test.go @@ -10,7 +10,7 @@ import ( authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -func TestAuthBasicCall(t *testing.T) { +func TestGatewayAuthBasicCall(t *testing.T) { loginResp, err := lib.BasicAuthClient.LoginWithResponse( t.Context(), authbasicapi.Orgid(alwaysOrgID), @@ -43,7 +43,7 @@ func TestAuthBasicCall(t *testing.T) { } } -func TestAuthBasicUnauthenticated(t *testing.T) { +func TestGatewayAuthBasicUnauthenticated(t *testing.T) { response := lib.Get( fmt.Sprintf("http://%s:%d/gw/backend/protected-echo/hi", lib.GetHost(), lib.GetPort()), t, @@ -74,7 +74,7 @@ func TestAuthBasicUnauthenticated(t *testing.T) { } } -func TestAuthBasicUnauthenticatedExempted(t *testing.T) { +func TestGatewayAuthBasicUnauthenticatedExempted(t *testing.T) { response := lib.Get( fmt.Sprintf("http://%s:%d/gw/backend/protected-echo/unprotected", lib.GetHost(), lib.GetPort()), t, @@ -106,7 +106,7 @@ func TestAuthBasicUnauthenticatedExempted(t *testing.T) { } } -func TestAuthBasicAuthorizedPleb(t *testing.T) { +func TestGatewayAuthBasicAuthorizedPleb(t *testing.T) { loginResp, err := lib.BasicAuthClient.LoginWithResponse( t.Context(), authbasicapi.Orgid(alwaysOrgID), diff --git a/test/suites/integration/state.go b/test/suites/integration/state.go index 1abd4b0..36742e1 100644 --- a/test/suites/integration/state.go +++ b/test/suites/integration/state.go @@ -8,6 +8,10 @@ var ( alwaysGroupDevID = 0 ) +// allPermissionIDs is the base set of all available admin group permissions. +// Tests that create admin groups should include these to avoid breaking permission-gated endpoints. +var allPermissionIDs = []int{1, 2, 3, 4, 5, 6, 7} + const ( // Always resource names, used to denote resource that all tests can expect to be present. // Always resource must never be altered or deleted by test cases, and are set up by test main. diff --git a/test/suites/security/cookies_test.go b/test/suites/security/cookies_test.go index afa4df1..f41563e 100644 --- a/test/suites/security/cookies_test.go +++ b/test/suites/security/cookies_test.go @@ -9,7 +9,7 @@ import ( authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -func TestCookies_admin(t *testing.T) { +func TestCookiesAdmin(t *testing.T) { t.Run("Verify superuser cookie attributes", func(t *testing.T) { t.Parallel() client := lib.AdminResponsesTLSClient(t, certDir) @@ -43,7 +43,7 @@ func TestCookies_admin(t *testing.T) { }) } -func TestCookies_basicauth(t *testing.T) { +func TestCookiesBasicAuth(t *testing.T) { t.Run("Verify basic auth cookie attributes", func(t *testing.T) { t.Parallel() client := lib.BasicAuthResponsesTLSClient(t, certDir) diff --git a/test/suites/security/cors_test.go b/test/suites/security/cors_test.go index 160eaf3..df33e47 100644 --- a/test/suites/security/cors_test.go +++ b/test/suites/security/cors_test.go @@ -13,7 +13,7 @@ import ( authbasicapi "github.com/trebent/kerberos/test/client/auth/basic" ) -func TestCORS_admin(t *testing.T) { +func TestCORSAdmin(t *testing.T) { t.Run("Non-browser request", func(t *testing.T) { t.Parallel() client := lib.AdminResponsesTLSClient(t, certDir) @@ -70,7 +70,7 @@ func TestCORS_admin(t *testing.T) { }) } -func TestCORS_basicauth(t *testing.T) { +func TestCORSBasicAuth(t *testing.T) { t.Run("Browser request - denied", func(t *testing.T) { t.Parallel() client := lib.BasicAuthResponsesTLSClient(t, certDir) @@ -99,7 +99,7 @@ func TestCORS_basicauth(t *testing.T) { }) } -func TestCORS_gateway(t *testing.T) { +func TestCORSGateway(t *testing.T) { t.Run("Non-browser request", func(t *testing.T) { t.Parallel() client := lib.TLSClient(t, certDir) diff --git a/test/suites/security/csrf_test.go b/test/suites/security/csrf_test.go deleted file mode 100644 index 3c8a4b4..0000000 --- a/test/suites/security/csrf_test.go +++ /dev/null @@ -1 +0,0 @@ -package security diff --git a/test/suites/security/tls_test.go b/test/suites/security/tls_test.go index ba229eb..23d7818 100644 --- a/test/suites/security/tls_test.go +++ b/test/suites/security/tls_test.go @@ -42,8 +42,8 @@ func TestAdminAPIPlainHTTP(t *testing.T) { // ---- Gateway API ---- -// TestGWAPITLS_mTLS_echo verifies that the gateway API is reachable over TLS towards an mTLS enabled backend. -func TestGWAPITLS_mTLS_echo(t *testing.T) { +// TestGWAPImTLSEcho verifies that the gateway API is reachable over TLS towards an mTLS enabled backend. +func TestGWAPImTLSEcho(t *testing.T) { t.Parallel() resp, err := lib.TLSClient(t, certDir).Get(fmt.Sprintf("https://localhost:%d/gw/backend/mtls-echo/hi", lib.GetPort())) @@ -57,8 +57,8 @@ func TestGWAPITLS_mTLS_echo(t *testing.T) { } } -// TestGWAPITLS_TLS_echo verifies that the gateway API is reachable over TLS towards a TLS enabled backend. -func TestGWAPITLS_TLS_echo(t *testing.T) { +// TestGWAPITLSEcho verifies that the gateway API is reachable over TLS towards a TLS enabled backend. +func TestGWAPITLSEcho(t *testing.T) { t.Parallel() resp, err := lib.TLSClient(t, certDir).Get(fmt.Sprintf("https://localhost:%d/gw/backend/tls-echo/hi", lib.GetPort())) From b7f09c196dc10622073f03107c727dce3b18978b Mon Sep 17 00:00:00 2001 From: maansaake Date: Sun, 23 Aug 2026 10:40:42 +0200 Subject: [PATCH 2/3] test: remove underscores from remaining unit test names Align the internal unit test names with the suite naming convention by dropping the Test_ underscores: - TestAuthorizer_Authenticated -> TestAuthorizerAuthenticated - TestAuthorizer_AuthorizedGroup -> TestAuthorizerAuthorizedGroup - TestPostgres_NamedArgs -> TestPostgresNamedArgs - TestSQLite_ForeignKey -> TestSQLiteForeignKey - TestValidateDirPath_File -> TestValidateDirPathFile - TestResponseHandler_UnknownError -> TestResponseHandlerUnknownError - TestRequestHandler_UnknownError -> TestRequestHandlerUnknownError Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/auth/method/basic/basic_test.go | 4 ++-- internal/db/postgres/postgres_test.go | 2 +- internal/db/sqlite/sqlite_test.go | 2 +- internal/env/validate_test.go | 2 +- internal/oapi/error/error_test.go | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/auth/method/basic/basic_test.go b/internal/auth/method/basic/basic_test.go index 6162ef4..c22d109 100644 --- a/internal/auth/method/basic/basic_test.go +++ b/internal/auth/method/basic/basic_test.go @@ -11,7 +11,7 @@ import ( authbasicapi "github.com/trebent/kerberos/internal/oapi/auth/basic" ) -func TestAuthorizer_Authenticated(t *testing.T) { +func TestAuthorizerAuthenticated(t *testing.T) { basic, err := New(&Opts{ AuthZConfig: map[string]*config.AuthZ{}, SQLClient: testClient, @@ -58,7 +58,7 @@ func TestAuthorizer_Authenticated(t *testing.T) { } } -func TestAuthorizer_AuthorizedGroup(t *testing.T) { +func TestAuthorizerAuthorizedGroup(t *testing.T) { groupName := uniqueName(t, "authZ-admin") basic, err := New(&Opts{ AuthZConfig: map[string]*config.AuthZ{ diff --git a/internal/db/postgres/postgres_test.go b/internal/db/postgres/postgres_test.go index ae56c1e..b94f684 100644 --- a/internal/db/postgres/postgres_test.go +++ b/internal/db/postgres/postgres_test.go @@ -64,7 +64,7 @@ func TestPostgres(t *testing.T) { } } -func TestPostgres_NamedArgs(t *testing.T) { +func TestPostgresNamedArgs(t *testing.T) { db := postgres.New(&postgres.Opts{DSN: dsn(t)}) _, err := db.Exec(t.Context(), "CREATE TABLE IF NOT EXISTS _test_pg_named (id SERIAL PRIMARY KEY, name TEXT NOT NULL)") diff --git a/internal/db/sqlite/sqlite_test.go b/internal/db/sqlite/sqlite_test.go index 1a7ed7c..9cc13dc 100644 --- a/internal/db/sqlite/sqlite_test.go +++ b/internal/db/sqlite/sqlite_test.go @@ -58,7 +58,7 @@ func TestSQLite(t *testing.T) { } } -func TestSQLite_ForeignKey(t *testing.T) { +func TestSQLiteForeignKey(t *testing.T) { dsn := "test_fk.db" defer func() { if err := os.Remove(dsn); err != nil { diff --git a/internal/env/validate_test.go b/internal/env/validate_test.go index 78c9ec8..e4eac31 100644 --- a/internal/env/validate_test.go +++ b/internal/env/validate_test.go @@ -35,7 +35,7 @@ func TestValidateDirPath(t *testing.T) { } } -func TestValidateDirPath_File(t *testing.T) { +func TestValidateDirPathFile(t *testing.T) { t.Parallel() dir := t.TempDir() diff --git a/internal/oapi/error/error_test.go b/internal/oapi/error/error_test.go index ab5d2bf..8f6ec06 100644 --- a/internal/oapi/error/error_test.go +++ b/internal/oapi/error/error_test.go @@ -22,7 +22,7 @@ func TestResponseHandler(t *testing.T) { } } -func TestResponseHandler_UnknownError(t *testing.T) { +func TestResponseHandlerUnknownError(t *testing.T) { recorder := httptest.NewRecorder() apierror.ResponseErrorHandler(recorder, nil, errors.New("unknown error")) @@ -50,7 +50,7 @@ func TestRequestHandler(t *testing.T) { } } -func TestRequestHandler_UnknownError(t *testing.T) { +func TestRequestHandlerUnknownError(t *testing.T) { recorder := httptest.NewRecorder() apierror.RequestErrorHandler(recorder, nil, errors.New("unknown error")) From 0bd1dc6afbed66ea3337d7e3fdce707dec446359 Mon Sep 17 00:00:00 2001 From: maansaake Date: Sun, 23 Aug 2026 10:44:31 +0200 Subject: [PATCH 3/3] test: move AllPermissionIDs from integration state to lib The admin permission-ID set is a shared test helper, so expose it as lib.AllPermissionIDs (in test/suites/lib/admin_user.go) instead of an unexported var in the integration suite's state.go. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../suites/integration/admin_api_flow_test.go | 2 +- .../integration/admin_api_groups_test.go | 26 +++++++++---------- test/suites/integration/admin_api_oas_test.go | 2 +- .../integration/admin_api_users_test.go | 12 ++++----- test/suites/integration/state.go | 4 --- test/suites/lib/admin_user.go | 4 +++ 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/test/suites/integration/admin_api_flow_test.go b/test/suites/integration/admin_api_flow_test.go index 02acdb0..4a6f776 100644 --- a/test/suites/integration/admin_api_flow_test.go +++ b/test/suites/integration/admin_api_flow_test.go @@ -88,7 +88,7 @@ func TestAdminGetFlowAsAdminUser(t *testing.T) { // Create a group with the flowviewer permission and assign the user to it. grpResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) diff --git a/test/suites/integration/admin_api_groups_test.go b/test/suites/integration/admin_api_groups_test.go index b295584..75fb7a3 100644 --- a/test/suites/integration/admin_api_groups_test.go +++ b/test/suites/integration/admin_api_groups_test.go @@ -16,7 +16,7 @@ func TestAdminGroupCreate(t *testing.T) { name := lib.GroupName() createResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -26,13 +26,13 @@ func TestAdminGroupCreate(t *testing.T) { t.Fatal("expected non-zero group ID in create response") } - if createResp.JSON201.Permissions == nil || len(*createResp.JSON201.Permissions) != len(allPermissionIDs) { + if createResp.JSON201.Permissions == nil || len(*createResp.JSON201.Permissions) != len(lib.AllPermissionIDs) { got := 0 if createResp.JSON201.Permissions != nil { got = len(*createResp.JSON201.Permissions) } t.Fatalf("expected %d permissions in create response, got %d", - len(allPermissionIDs), got) + len(lib.AllPermissionIDs), got) } } @@ -44,7 +44,7 @@ func TestAdminGroupCreateConflict(t *testing.T) { name := lib.GroupName() createResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -52,7 +52,7 @@ func TestAdminGroupCreateConflict(t *testing.T) { dupResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -68,7 +68,7 @@ func TestAdminGroupList(t *testing.T) { name := lib.GroupName() createResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -98,7 +98,7 @@ func TestAdminGroupGet(t *testing.T) { name := lib.GroupName() createResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -139,7 +139,7 @@ func TestAdminGroupUpdate(t *testing.T) { name := lib.GroupName() createResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -150,7 +150,7 @@ func TestAdminGroupUpdate(t *testing.T) { updateResp, err := lib.AdminClient.UpdateGroupWithResponse( t.Context(), groupID, - adminapi.UpdateGroupJSONRequestBody{Name: newName, PermissionIDs: allPermissionIDs}, + adminapi.UpdateGroupJSONRequestBody{Name: newName, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -174,7 +174,7 @@ func TestAdminGroupUpdateConflict(t *testing.T) { name := lib.GroupName() createResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -183,7 +183,7 @@ func TestAdminGroupUpdateConflict(t *testing.T) { name2 := lib.GroupName() createResp2, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name2, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name2, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -194,7 +194,7 @@ func TestAdminGroupUpdateConflict(t *testing.T) { updateResp, err := lib.AdminClient.UpdateGroupWithResponse( t.Context(), groupID, - adminapi.UpdateGroupJSONRequestBody{Name: name2, PermissionIDs: allPermissionIDs}, + adminapi.UpdateGroupJSONRequestBody{Name: name2, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -210,7 +210,7 @@ func TestAdminGroupDelete(t *testing.T) { name := lib.GroupName() createResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: name, PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) diff --git a/test/suites/integration/admin_api_oas_test.go b/test/suites/integration/admin_api_oas_test.go index 8f19719..b6e1fd6 100644 --- a/test/suites/integration/admin_api_oas_test.go +++ b/test/suites/integration/admin_api_oas_test.go @@ -54,7 +54,7 @@ func TestAdminGetBackendOASAsAdminUser(t *testing.T) { // Create a group with the oasviewer permission and assign the user to it. grpResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) diff --git a/test/suites/integration/admin_api_users_test.go b/test/suites/integration/admin_api_users_test.go index b69b747..7c96b26 100644 --- a/test/suites/integration/admin_api_users_test.go +++ b/test/suites/integration/admin_api_users_test.go @@ -245,7 +245,7 @@ func TestAdminUserGroupBindingsAssign(t *testing.T) { grp1Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -254,7 +254,7 @@ func TestAdminUserGroupBindingsAssign(t *testing.T) { grp2Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -308,7 +308,7 @@ func TestAdminUserGroupBindingsUpdate(t *testing.T) { grp1Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -317,7 +317,7 @@ func TestAdminUserGroupBindingsUpdate(t *testing.T) { grp2Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -326,7 +326,7 @@ func TestAdminUserGroupBindingsUpdate(t *testing.T) { grp3Resp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) @@ -395,7 +395,7 @@ func TestAdminUserGroupBindingsClear(t *testing.T) { grpResp, err := lib.AdminClient.CreateGroupWithResponse( t.Context(), - adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: allPermissionIDs}, + adminapi.CreateGroupJSONRequestBody{Name: lib.GroupName(), PermissionIDs: lib.AllPermissionIDs}, adminapi.RequestEditorFn(superRequestEditor), ) lib.CheckErr(err, t) diff --git a/test/suites/integration/state.go b/test/suites/integration/state.go index 36742e1..1abd4b0 100644 --- a/test/suites/integration/state.go +++ b/test/suites/integration/state.go @@ -8,10 +8,6 @@ var ( alwaysGroupDevID = 0 ) -// allPermissionIDs is the base set of all available admin group permissions. -// Tests that create admin groups should include these to avoid breaking permission-gated endpoints. -var allPermissionIDs = []int{1, 2, 3, 4, 5, 6, 7} - const ( // Always resource names, used to denote resource that all tests can expect to be present. // Always resource must never be altered or deleted by test cases, and are set up by test main. diff --git a/test/suites/lib/admin_user.go b/test/suites/lib/admin_user.go index 1a0bded..0b7d8ef 100644 --- a/test/suites/lib/admin_user.go +++ b/test/suites/lib/admin_user.go @@ -7,6 +7,10 @@ import ( adminapi "github.com/trebent/kerberos/test/client/admin" ) +// AllPermissionIDs is the base set of all available admin group permissions. +// Tests that create admin groups should include these to avoid breaking permission-gated endpoints. +var AllPermissionIDs = []int{1, 2, 3, 4, 5, 6, 7} + // MustGetAdminUserID fetches the admin user list and returns the ID of the user with the given username. func MustGetAdminUserID(t *testing.T, requestEditor RequestEditorFn, name string) int { t.Helper()