e3d9047143
DEVOPS-722 enforce team management visibility * DEVOPS-722 enforce team management visibility * DEVOPS-722 reduce admin user list complexity * DEVOPS-722 cover admin access helpers * DEVOPS-722 address team management PR feedback * DEVOPS-722 fix admin access tests in CI * DEVOPS-722 fix remaining admin PR feedback
495 lines
16 KiB
Go
495 lines
16 KiB
Go
package usermanagement
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// createMockPermitServer creates a mock server with scope endpoint and optional additional handlers.
|
|
// It returns the server and a PermitConfig configured to use it.
|
|
func createMockPermitServer(t *testing.T, additionalHandlers map[string]http.HandlerFunc) (*httptest.Server, *PermitConfig) {
|
|
mux := http.NewServeMux()
|
|
|
|
// Always add the scope handler
|
|
mux.HandleFunc("/v2/api-key/scope", func(w http.ResponseWriter, r *http.Request) {
|
|
response := map[string]string{
|
|
"organization_id": "test-org",
|
|
"project_id": "test-project",
|
|
"environment_id": "test-env",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
err := json.NewEncoder(w).Encode(response)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
// Add any additional handlers
|
|
for path, handler := range additionalHandlers {
|
|
mux.HandleFunc(path, handler)
|
|
}
|
|
|
|
server := httptest.NewServer(mux)
|
|
|
|
config := &PermitConfig{
|
|
APIKey: "test-api-key",
|
|
BaseURL: server.URL,
|
|
}
|
|
|
|
return server, config
|
|
}
|
|
|
|
// TestGetAPIKeyScope_Success tests successful API key scope retrieval
|
|
func TestGetAPIKeyScope_Success(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "GET", r.Method)
|
|
assert.Equal(t, "/v2/api-key/scope", r.URL.Path)
|
|
assert.Equal(t, "Bearer test-api-key", r.Header.Get("Authorization"))
|
|
|
|
response := map[string]string{
|
|
"organization_id": "org-123",
|
|
"project_id": "proj-456",
|
|
"environment_id": "env-789",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
err := json.NewEncoder(w).Encode(response)
|
|
require.NoError(t, err)
|
|
}))
|
|
defer server.Close()
|
|
|
|
scope, err := GetAPIKeyScope(server.Client(), "test-api-key", server.URL)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, scope)
|
|
assert.Equal(t, "org-123", scope.OrganizationID)
|
|
assert.Equal(t, "proj-456", scope.ProjectID)
|
|
assert.Equal(t, "env-789", scope.EnvironmentID)
|
|
}
|
|
|
|
func TestGetPermitUserRolesEscapesUserKey(t *testing.T) {
|
|
server, config := createMockPermitServer(t, map[string]http.HandlerFunc{
|
|
"/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "jacob+2@mathisonprojects.com", r.URL.Query().Get("user"))
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`[{"role":"super_admin","user":"jacob+2@mathisonprojects.com","tenant":"default"}]`))
|
|
require.NoError(t, err)
|
|
},
|
|
})
|
|
defer server.Close()
|
|
|
|
roles, err := GetPermitUserRoles(server.Client(), config, "jacob+2@mathisonprojects.com")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"super_admin"}, roles)
|
|
}
|
|
|
|
func TestListAllPermitRoleAssignmentsDirectArray(t *testing.T) {
|
|
requests := 0
|
|
server, config := createMockPermitServer(t, map[string]http.HandlerFunc{
|
|
"/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) {
|
|
requests++
|
|
assert.Empty(t, r.URL.Query().Get("user"))
|
|
assert.Equal(t, "1", r.URL.Query().Get("page"))
|
|
assert.Equal(t, "100", r.URL.Query().Get("per_page"))
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`[{"role":"client_user","user":"sub-1","tenant":"default"},{"role":"user_admin","user":"sub-1","tenant":"default"}]`))
|
|
require.NoError(t, err)
|
|
},
|
|
})
|
|
defer server.Close()
|
|
|
|
assignments, err := ListAllPermitRoleAssignments(server.Client(), config)
|
|
|
|
require.NoError(t, err)
|
|
require.Len(t, assignments, 2)
|
|
assert.Equal(t, "client_user", assignments[0].Role)
|
|
assert.Equal(t, "sub-1", assignments[0].User)
|
|
assert.Equal(t, 1, requests)
|
|
}
|
|
|
|
func TestListAllPermitRoleAssignmentsObjectWrappedPagination(t *testing.T) {
|
|
requests := 0
|
|
server, config := createMockPermitServer(t, map[string]http.HandlerFunc{
|
|
"/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) {
|
|
requests++
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
if r.URL.Query().Get("page") == "1" {
|
|
page := make([]map[string]string, 0, 100)
|
|
for i := 0; i < 100; i++ {
|
|
page = append(page, map[string]string{"role": "client_user", "user": "sub-page-1", "tenant": "default"})
|
|
}
|
|
err := json.NewEncoder(w).Encode(map[string]interface{}{"data": page})
|
|
require.NoError(t, err)
|
|
return
|
|
}
|
|
err := json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"data": []map[string]string{{"role": "auditor", "user": "sub-page-2", "tenant": "default"}},
|
|
})
|
|
require.NoError(t, err)
|
|
},
|
|
})
|
|
defer server.Close()
|
|
|
|
assignments, err := ListAllPermitRoleAssignments(server.Client(), config)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, assignments, 101)
|
|
assert.Equal(t, "auditor", assignments[100].Role)
|
|
assert.Equal(t, "sub-page-2", assignments[100].User)
|
|
assert.Equal(t, 2, requests)
|
|
}
|
|
|
|
func TestListAllPermitRoleAssignmentsRejectsUnknownSuccessfulShape(t *testing.T) {
|
|
server, config := createMockPermitServer(t, map[string]http.HandlerFunc{
|
|
"/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`{"unexpected":[]}`))
|
|
require.NoError(t, err)
|
|
},
|
|
})
|
|
defer server.Close()
|
|
|
|
assignments, err := ListAllPermitRoleAssignments(server.Client(), config)
|
|
|
|
require.Error(t, err)
|
|
assert.Nil(t, assignments)
|
|
assert.Contains(t, err.Error(), "unexpected Permit.io role assignments response format")
|
|
}
|
|
|
|
// TestGetAPIKeyScope_OrgLevelKey tests API key with only organization scope
|
|
func TestGetAPIKeyScope_OrgLevelKey(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
response := map[string]string{
|
|
"organization_id": "org-123",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
err := json.NewEncoder(w).Encode(response)
|
|
require.NoError(t, err)
|
|
}))
|
|
defer server.Close()
|
|
|
|
scope, err := GetAPIKeyScope(server.Client(), "test-api-key", server.URL)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, scope)
|
|
assert.Equal(t, "org-123", scope.OrganizationID)
|
|
assert.Empty(t, scope.ProjectID)
|
|
assert.Empty(t, scope.EnvironmentID)
|
|
}
|
|
|
|
// TestGetAPIKeyScope_Unauthorized tests unauthorized API key
|
|
func TestGetAPIKeyScope_Unauthorized(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
_, err := w.Write([]byte(`{"error": "Invalid API key"}`))
|
|
require.NoError(t, err)
|
|
}))
|
|
defer server.Close()
|
|
|
|
scope, err := GetAPIKeyScope(server.Client(), "invalid-key", server.URL)
|
|
|
|
assert.Error(t, err)
|
|
assert.Nil(t, scope)
|
|
assert.Contains(t, err.Error(), "API error (status 401)")
|
|
}
|
|
|
|
// TestPermitConfig_GetProjectID tests the GetProjectID method
|
|
func TestPermitConfig_GetProjectID(t *testing.T) {
|
|
server, config := createMockPermitServer(t, nil)
|
|
defer server.Close()
|
|
|
|
projectID, err := config.GetProjectID(server.Client())
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-project", projectID)
|
|
}
|
|
|
|
// TestPermitConfig_GetEnvID tests the GetEnvID method
|
|
func TestPermitConfig_GetEnvID(t *testing.T) {
|
|
server, config := createMockPermitServer(t, nil)
|
|
defer server.Close()
|
|
|
|
envID, err := config.GetEnvID(server.Client())
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-env", envID)
|
|
}
|
|
|
|
// TestPermitConfig_GetProjectID_OrgLevelKey tests GetProjectID with org-level key
|
|
func TestPermitConfig_GetProjectID_OrgLevelKey(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
response := map[string]string{
|
|
"organization_id": "org-123",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
err := json.NewEncoder(w).Encode(response)
|
|
require.NoError(t, err)
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &PermitConfig{
|
|
APIKey: "test-api-key",
|
|
BaseURL: server.URL,
|
|
}
|
|
|
|
projectID, err := config.GetProjectID(server.Client())
|
|
|
|
assert.Error(t, err)
|
|
assert.Empty(t, projectID)
|
|
assert.ErrorIs(t, err, ErrOrgLevelKey)
|
|
}
|
|
|
|
// TestGetRoleDetails_Success tests successful role details retrieval
|
|
func TestGetRoleDetails_Success(t *testing.T) {
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/v2/schema/test-project/test-env/roles/auditor": func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "GET", r.Method)
|
|
assert.Equal(t, "Bearer test-api-key", r.Header.Get("Authorization"))
|
|
|
|
response := map[string]interface{}{
|
|
"key": "auditor",
|
|
"name": "Auditor",
|
|
"description": "Read-only access to view documents",
|
|
"permissions": []string{"document:read", "document:list", "export:read"},
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
err := json.NewEncoder(w).Encode(response)
|
|
require.NoError(t, err)
|
|
},
|
|
}
|
|
|
|
server, config := createMockPermitServer(t, handlers)
|
|
defer server.Close()
|
|
|
|
roleDetails, err := GetRoleDetails(server.Client(), config, "auditor")
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, roleDetails)
|
|
assert.Equal(t, "auditor", roleDetails.Key)
|
|
assert.Equal(t, "Auditor", roleDetails.Name)
|
|
assert.Equal(t, "Read-only access to view documents", roleDetails.Description)
|
|
assert.Equal(t, []string{"document:read", "document:list", "export:read"}, roleDetails.Permissions)
|
|
}
|
|
|
|
// TestGetRoleDetails_EmptyPermissions tests role with no permissions
|
|
func TestGetRoleDetails_EmptyPermissions(t *testing.T) {
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/v2/schema/test-project/test-env/roles/viewer": func(w http.ResponseWriter, r *http.Request) {
|
|
response := map[string]interface{}{
|
|
"key": "viewer",
|
|
"name": "Viewer",
|
|
"description": "",
|
|
"permissions": []string{},
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
err := json.NewEncoder(w).Encode(response)
|
|
require.NoError(t, err)
|
|
},
|
|
}
|
|
|
|
server, config := createMockPermitServer(t, handlers)
|
|
defer server.Close()
|
|
|
|
roleDetails, err := GetRoleDetails(server.Client(), config, "viewer")
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, roleDetails)
|
|
assert.Equal(t, "viewer", roleDetails.Key)
|
|
assert.Equal(t, "Viewer", roleDetails.Name)
|
|
assert.Empty(t, roleDetails.Description)
|
|
assert.Empty(t, roleDetails.Permissions)
|
|
}
|
|
|
|
// TestGetRoleDetails_NilPermissions tests role with nil permissions field
|
|
func TestGetRoleDetails_NilPermissions(t *testing.T) {
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/v2/schema/test-project/test-env/roles/basic": func(w http.ResponseWriter, r *http.Request) {
|
|
response := map[string]interface{}{
|
|
"key": "basic",
|
|
"name": "Basic User",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
err := json.NewEncoder(w).Encode(response)
|
|
require.NoError(t, err)
|
|
},
|
|
}
|
|
|
|
server, config := createMockPermitServer(t, handlers)
|
|
defer server.Close()
|
|
|
|
roleDetails, err := GetRoleDetails(server.Client(), config, "basic")
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, roleDetails)
|
|
assert.Equal(t, "basic", roleDetails.Key)
|
|
assert.Equal(t, "Basic User", roleDetails.Name)
|
|
assert.Nil(t, roleDetails.Permissions)
|
|
}
|
|
|
|
// TestGetRoleDetails_NotFound tests role not found error
|
|
func TestGetRoleDetails_NotFound(t *testing.T) {
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/v2/schema/test-project/test-env/roles/nonexistent": func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
_, err := w.Write([]byte(`{"error": "Role not found"}`))
|
|
require.NoError(t, err)
|
|
},
|
|
}
|
|
|
|
server, config := createMockPermitServer(t, handlers)
|
|
defer server.Close()
|
|
|
|
roleDetails, err := GetRoleDetails(server.Client(), config, "nonexistent")
|
|
|
|
assert.Error(t, err)
|
|
assert.Nil(t, roleDetails)
|
|
assert.Contains(t, err.Error(), "API error (status 404)")
|
|
}
|
|
|
|
// TestGetRoleDetails_Unauthorized tests unauthorized error
|
|
func TestGetRoleDetails_Unauthorized(t *testing.T) {
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/v2/schema/test-project/test-env/roles/auditor": func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
_, err := w.Write([]byte(`{"error": "Invalid API key"}`))
|
|
require.NoError(t, err)
|
|
},
|
|
}
|
|
|
|
server, config := createMockPermitServer(t, handlers)
|
|
defer server.Close()
|
|
|
|
roleDetails, err := GetRoleDetails(server.Client(), config, "auditor")
|
|
|
|
assert.Error(t, err)
|
|
assert.Nil(t, roleDetails)
|
|
assert.Contains(t, err.Error(), "API error (status 401)")
|
|
}
|
|
|
|
// TestGetRoleDetails_InvalidJSON tests invalid JSON response handling
|
|
func TestGetRoleDetails_InvalidJSON(t *testing.T) {
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/v2/schema/test-project/test-env/roles/auditor": func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`invalid json`))
|
|
require.NoError(t, err)
|
|
},
|
|
}
|
|
|
|
server, config := createMockPermitServer(t, handlers)
|
|
defer server.Close()
|
|
|
|
roleDetails, err := GetRoleDetails(server.Client(), config, "auditor")
|
|
|
|
assert.Error(t, err)
|
|
assert.Nil(t, roleDetails)
|
|
assert.Contains(t, err.Error(), "error parsing response")
|
|
}
|
|
|
|
// TestGetRoleDetails_ConnectionError tests connection error handling
|
|
func TestGetRoleDetails_ConnectionError(t *testing.T) {
|
|
config := &PermitConfig{
|
|
APIKey: "test-api-key",
|
|
BaseURL: "http://nonexistent-host:9999",
|
|
}
|
|
|
|
client := &http.Client{}
|
|
roleDetails, err := GetRoleDetails(client, config, "auditor")
|
|
|
|
assert.Error(t, err)
|
|
assert.Nil(t, roleDetails)
|
|
// Error will be from GetProjectID trying to fetch scope
|
|
assert.Contains(t, err.Error(), "failed to get project ID")
|
|
}
|
|
|
|
// TestCreatePermitUser_IncludesEnvironmentID verifies that CreatePermitUser includes
|
|
// the environment_id attribute when the CognitoUserResponse has a non-empty EnvironmentID.
|
|
func TestCreatePermitUser_IncludesEnvironmentID(t *testing.T) {
|
|
var capturedBody PermitUser
|
|
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/v2/facts/test-project/test-env/users": func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "POST", r.Method)
|
|
err := json.NewDecoder(r.Body).Decode(&capturedBody)
|
|
require.NoError(t, err)
|
|
w.WriteHeader(http.StatusCreated)
|
|
},
|
|
}
|
|
|
|
server, config := createMockPermitServer(t, handlers)
|
|
defer server.Close()
|
|
|
|
cognitoUser := &CognitoUserResponse{
|
|
SubjectID: "sub-123",
|
|
Username: "test@example.com",
|
|
Email: "test@example.com",
|
|
FirstName: "Test",
|
|
LastName: "User",
|
|
Status: "CONFIRMED",
|
|
Enabled: true,
|
|
EnvironmentID: "acmehealth",
|
|
}
|
|
|
|
err := CreatePermitUser(server.Client(), config, cognitoUser)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "acmehealth", capturedBody.Attributes["environment_id"],
|
|
"environment_id attribute should be set when user has EnvironmentID")
|
|
assert.Equal(t, "test@example.com", capturedBody.Attributes["cognito_username"])
|
|
assert.Equal(t, "CONFIRMED", capturedBody.Attributes["cognito_status"])
|
|
}
|
|
|
|
// TestCreatePermitUser_OmitsEnvironmentIDWhenEmpty verifies that CreatePermitUser
|
|
// does NOT include environment_id in attributes when EnvironmentID is empty.
|
|
func TestCreatePermitUser_OmitsEnvironmentIDWhenEmpty(t *testing.T) {
|
|
var capturedBody PermitUser
|
|
|
|
handlers := map[string]http.HandlerFunc{
|
|
"/v2/facts/test-project/test-env/users": func(w http.ResponseWriter, r *http.Request) {
|
|
err := json.NewDecoder(r.Body).Decode(&capturedBody)
|
|
require.NoError(t, err)
|
|
w.WriteHeader(http.StatusCreated)
|
|
},
|
|
}
|
|
|
|
server, config := createMockPermitServer(t, handlers)
|
|
defer server.Close()
|
|
|
|
cognitoUser := &CognitoUserResponse{
|
|
SubjectID: "sub-456",
|
|
Username: "legacy@example.com",
|
|
Email: "legacy@example.com",
|
|
FirstName: "Legacy",
|
|
LastName: "User",
|
|
Status: "CONFIRMED",
|
|
Enabled: true,
|
|
EnvironmentID: "", // empty -- should NOT appear
|
|
}
|
|
|
|
err := CreatePermitUser(server.Client(), config, cognitoUser)
|
|
require.NoError(t, err)
|
|
|
|
_, hasEnvID := capturedBody.Attributes["environment_id"]
|
|
assert.False(t, hasEnvID,
|
|
"environment_id attribute should NOT be present when user has no EnvironmentID")
|
|
assert.Equal(t, "legacy@example.com", capturedBody.Attributes["cognito_username"])
|
|
}
|