Files
query-orchestration/internal/usermanagement/permitio.go
T
Jay Brown c668485e6f Merged in feature/cognito-shared-environments (pull request #211)
cognito and permit shared prod environment support

* code complete

* user creattion tool

test harness
2026-02-26 12:33:35 +00:00

814 lines
26 KiB
Go

// permitio.go handles Permit.io API operations for user management and role-based access control (RBAC).
// This file provides functions to create, delete, search, and manage users in Permit.io, as well as
// assign/unassign roles and validate role definitions.
package usermanagement
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// CreatePermitUser creates a new user in Permit.io using data from a Cognito user.
// The Cognito Subject ID is used as the Permit.io user key for consistency across systems.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - cognitoUser: The Cognito user response containing user details and Subject ID
//
// Returns:
// - error: Error if the user creation fails. Returns nil if user is created or already exists.
func CreatePermitUser(client *http.Client, config *PermitConfig, cognitoUser *CognitoUserResponse) error {
projectID, err := config.GetProjectID(client)
if err != nil {
return fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return fmt.Errorf("failed to get environment ID: %w", err)
}
url := fmt.Sprintf("%s/v2/facts/%s/%s/users", config.BaseURL, projectID, envID)
attrs := map[string]interface{}{
"cognito_username": cognitoUser.Username,
"cognito_status": cognitoUser.Status,
}
if cognitoUser.EnvironmentID != "" {
attrs["environment_id"] = cognitoUser.EnvironmentID
}
userObj := PermitUser{
Key: cognitoUser.SubjectID, // Use Cognito Subject ID as the key
Email: cognitoUser.Email,
FirstName: cognitoUser.FirstName,
LastName: cognitoUser.LastName,
Attributes: attrs,
}
jsonData, err := json.Marshal(userObj)
if err != nil {
return fmt.Errorf("error marshaling user data: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
// If we get a 404, provide helpful debugging info
if resp.StatusCode == http.StatusNotFound {
var errResp map[string]interface{}
if err := json.Unmarshal(body, &errResp); err == nil {
return fmt.Errorf("API error (status 404): %v", errResp["message"])
}
}
if resp.StatusCode == http.StatusConflict || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated {
// User already exists or was created successfully
return nil
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
var errResp map[string]interface{}
if err := json.Unmarshal(body, &errResp); err == nil {
if errorObj, ok := errResp["error"].(map[string]interface{}); ok {
if msg, ok := errorObj["message"].(string); ok {
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, msg)
}
}
}
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
return nil
}
// GetPermitUser retrieves a user from Permit.io by their user key, returning the full user
// object including custom attributes. This is useful for verifying that attributes like
// environment_id were correctly stored when the user was created.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - userKey: The Permit.io user key (typically Cognito SubjectID)
//
// Returns:
// - *PermitUser: The full user object including attributes
// - error: Error if the retrieval fails or user is not found
func GetPermitUser(client *http.Client, config *PermitConfig, userKey string) (*PermitUser, error) {
projectID, err := config.GetProjectID(client)
if err != nil {
return nil, fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return nil, fmt.Errorf("failed to get environment ID: %w", err)
}
url := fmt.Sprintf("%s/v2/facts/%s/%s/users/%s", config.BaseURL, projectID, envID, userKey)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
var user PermitUser
if err := json.Unmarshal(body, &user); err != nil {
return nil, fmt.Errorf("error parsing response: %w", err)
}
return &user, nil
}
// DeletePermitUser permanently deletes a user from Permit.io by their user key.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - userKey: The Permit.io user key (typically Cognito Subject ID)
//
// Returns:
// - error: Error if the deletion fails
func DeletePermitUser(client *http.Client, config *PermitConfig, userKey string) error {
projectID, err := config.GetProjectID(client)
if err != nil {
return fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return fmt.Errorf("failed to get environment ID: %w", err)
}
url := fmt.Sprintf("%s/v2/facts/%s/%s/users/%s", config.BaseURL, projectID, envID, userKey)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
var errResp map[string]interface{}
if err := json.Unmarshal(body, &errResp); err == nil {
if errorObj, ok := errResp["error"].(map[string]interface{}); ok {
if msg, ok := errorObj["message"].(string); ok {
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, msg)
}
}
}
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
return nil
}
// FindPermitUserByEmail searches for a user in Permit.io by their email address.
// Returns the user's key (typically Cognito Subject ID) if found.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - email: The email address to search for
//
// Returns:
// - string: The Permit.io user key if found, empty string if not found
// - error: Error if the search fails
func FindPermitUserByEmail(client *http.Client, config *PermitConfig, email string) (string, error) {
projectID, err := config.GetProjectID(client)
if err != nil {
return "", fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return "", fmt.Errorf("failed to get environment ID: %w", err)
}
// Search for user by email
url := fmt.Sprintf("%s/v2/facts/%s/%s/users?search=%s", config.BaseURL, projectID, envID, email)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
// Parse response to find user
var response map[string]interface{}
if err := json.Unmarshal(body, &response); err != nil {
return "", fmt.Errorf("error parsing response: %w", err)
}
// Look for users in common pagination structures
var users []interface{}
if data, ok := response["data"].([]interface{}); ok {
users = data
} else if items, ok := response["items"].([]interface{}); ok {
users = items
} else if usersList, ok := response["users"].([]interface{}); ok {
users = usersList
} else if _, ok := response["key"]; ok {
// Single user object returned
users = []interface{}{response}
} else {
// Try direct array unmarshaling as fallback
var directUsers []interface{}
if err := json.Unmarshal(body, &directUsers); err == nil {
users = directUsers
} else {
return "", fmt.Errorf("unexpected response format")
}
}
// Find user with matching email
for _, u := range users {
if user, ok := u.(map[string]interface{}); ok {
if userEmail, ok := user["email"].(string); ok && userEmail == email {
if key, ok := user["key"].(string); ok {
return key, nil
}
}
}
}
return "", nil // User not found
}
// UserExistsInPermit checks if a user exists in Permit.io by email.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - email: The email address to check
//
// Returns:
// - bool: true if the user exists, false otherwise
// - string: The user's key if found, empty string otherwise
// - error: Error if the check fails
func UserExistsInPermit(client *http.Client, config *PermitConfig, email string) (bool, string, error) {
userKey, err := FindPermitUserByEmail(client, config, email)
if err != nil {
return false, "", err
}
return userKey != "", userKey, nil
}
// AssignRole assigns a role to a user in Permit.io for the configured tenant.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials, project details, and default tenant
// - userID: The Permit.io user key (typically Cognito Subject ID)
// - role: The role key to assign
//
// Returns:
// - error: Error if the role assignment fails. Returns nil if role is assigned or already assigned.
func AssignRole(client *http.Client, config *PermitConfig, userID, role string) error {
projectID, err := config.GetProjectID(client)
if err != nil {
return fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return fmt.Errorf("failed to get environment ID: %w", err)
}
url := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments", config.BaseURL, projectID, envID)
tenant := config.DefaultTenant
if tenant == "" {
tenant = "default"
}
assignment := RoleAssignment{
User: userID,
Role: role,
Tenant: tenant,
}
jsonData, err := json.Marshal(assignment)
if err != nil {
return fmt.Errorf("error marshaling role assignment: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusConflict || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated {
// Role already assigned or was assigned successfully
return nil
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
var errResp map[string]interface{}
if err := json.Unmarshal(body, &errResp); err == nil {
if errorObj, ok := errResp["error"].(map[string]interface{}); ok {
if msg, ok := errorObj["message"].(string); ok {
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, msg)
}
}
}
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
return nil
}
// UnassignRole removes a role assignment from a user in Permit.io.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials, project details, and default tenant
// - userID: The Permit.io user key (typically Cognito Subject ID)
// - role: The role key to unassign
//
// Returns:
// - error: Error if the role unassignment fails. Returns nil if role is unassigned or was already not assigned.
func UnassignRole(client *http.Client, config *PermitConfig, userID, role string) error {
projectID, err := config.GetProjectID(client)
if err != nil {
return fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return fmt.Errorf("failed to get environment ID: %w", err)
}
url := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments", config.BaseURL, projectID, envID)
tenant := config.DefaultTenant
if tenant == "" {
tenant = "default"
}
assignment := RoleAssignment{
User: userID,
Role: role,
Tenant: tenant,
}
jsonData, err := json.Marshal(assignment)
if err != nil {
return fmt.Errorf("error marshaling role assignment: %w", err)
}
req, err := http.NewRequest("DELETE", url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent || resp.StatusCode == http.StatusNotFound {
// Role was unassigned, already unassigned, or didn't exist
return nil
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
var errResp map[string]interface{}
if err := json.Unmarshal(body, &errResp); err == nil {
if errorObj, ok := errResp["error"].(map[string]interface{}); ok {
if msg, ok := errorObj["message"].(string); ok {
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, msg)
}
}
}
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
return nil
}
// GetPermitUserRoles retrieves all roles assigned to a user in Permit.io.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - userKey: The Permit.io user key (typically Cognito Subject ID)
//
// Returns:
// - []string: List of role keys assigned to the user
// - error: Error if the retrieval fails
func GetPermitUserRoles(client *http.Client, config *PermitConfig, userKey string) ([]string, error) {
projectID, err := config.GetProjectID(client)
if err != nil {
return nil, fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return nil, fmt.Errorf("failed to get environment ID: %w", err)
}
url := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments?user=%s",
config.BaseURL, projectID, envID, userKey)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response: %w", err)
}
roles := []string{} // Initialize as empty slice, not nil
var assignments []interface{}
// First try to unmarshal as direct array (common case)
var directAssignments []interface{}
if err := json.Unmarshal(body, &directAssignments); err == nil {
assignments = directAssignments
} else {
// If that fails, try as object with data/items/etc
var response map[string]interface{}
if err := json.Unmarshal(body, &response); err != nil {
return nil, fmt.Errorf("error parsing response as object or array: %w", err)
}
// Look for role assignments in common response structures
if data, ok := response["data"].([]interface{}); ok {
assignments = data
} else if items, ok := response["items"].([]interface{}); ok {
assignments = items
} else if results, ok := response["results"].([]interface{}); ok {
assignments = results
}
}
// Extract role names from assignments
for _, assignment := range assignments {
if roleAssignment, ok := assignment.(map[string]interface{}); ok {
if role, ok := roleAssignment["role"].(string); ok {
roles = append(roles, role)
}
}
}
return roles, nil
}
// GetExistingRoles retrieves all role definitions from the specified Permit.io project/environment.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
//
// Returns:
// - []string: List of role keys that exist in the Permit.io environment
// - error: Error if the retrieval fails
func GetExistingRoles(client *http.Client, config *PermitConfig) ([]string, error) {
projectID, err := config.GetProjectID(client)
if err != nil {
return nil, fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return nil, fmt.Errorf("failed to get environment ID: %w", err)
}
// Construct API URL for roles endpoint
url := fmt.Sprintf("%s/v2/schema/%s/%s/roles", config.BaseURL, projectID, envID)
// Create HTTP request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
// Set authorization header
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
// Execute request
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
// Check for successful response
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
// Read and parse response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response: %w", err)
}
// Parse JSON response - try different response structures
var roles []string
var rolesData []interface{}
// First try to unmarshal as direct array
var directRoles []interface{}
if err := json.Unmarshal(body, &directRoles); err == nil {
rolesData = directRoles
} else {
// If that fails, try as object with data/items/etc
var response map[string]interface{}
if err := json.Unmarshal(body, &response); err != nil {
return nil, fmt.Errorf("error parsing response as object or array: %w", err)
}
// Look for roles in common response structures
if data, ok := response["data"].([]interface{}); ok {
rolesData = data
} else if items, ok := response["items"].([]interface{}); ok {
rolesData = items
} else if results, ok := response["results"].([]interface{}); ok {
rolesData = results
} else {
return nil, fmt.Errorf("unexpected response format: unable to find roles array")
}
}
// Extract role names from the data
for _, roleData := range rolesData {
if roleObj, ok := roleData.(map[string]interface{}); ok {
// Try different possible field names for role identifier
if key, ok := roleObj["key"].(string); ok {
roles = append(roles, key)
} else if name, ok := roleObj["name"].(string); ok {
roles = append(roles, name)
} else if id, ok := roleObj["id"].(string); ok {
roles = append(roles, id)
}
}
}
return roles, nil
}
// GetUserRoles is an alias for GetPermitUserRoles for convenience.
// Retrieves all roles assigned to a user in Permit.io.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - userKey: The Permit.io user key (typically Cognito Subject ID)
//
// Returns:
// - []string: List of role keys assigned to the user
// - error: Error if the retrieval fails
func GetUserRoles(client *http.Client, config *PermitConfig, userKey string) ([]string, error) {
return GetPermitUserRoles(client, config, userKey)
}
// ValidateRoles validates that all requested roles exist in the Permit.io environment.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - requestedRoles: List of role keys to validate
//
// Returns:
// - []string: List of missing roles (empty if all roles exist)
// - error: Error if the validation check fails
func ValidateRoles(client *http.Client, config *PermitConfig, requestedRoles []string) ([]string, error) {
if len(requestedRoles) == 0 {
return []string{}, nil // No roles to validate
}
// Get all existing roles from Permit.io
existingRoles, err := GetExistingRoles(client, config)
if err != nil {
return requestedRoles, fmt.Errorf("failed to retrieve existing roles from Permit.io: %w", err)
}
// Create a set of existing roles for efficient lookup
existingSet := make(map[string]bool)
for _, role := range existingRoles {
existingSet[role] = true
}
// Find requested roles that don't exist
var missingRoles []string
for _, requestedRole := range requestedRoles {
if !existingSet[requestedRole] {
missingRoles = append(missingRoles, requestedRole)
}
}
if len(missingRoles) > 0 {
return missingRoles, fmt.Errorf("the following roles do not exist in Permit.io: %s",
strings.Join(missingRoles, ", "))
}
return []string{}, nil
}
// RoleDetails contains the details of a role including its permissions.
type RoleDetails struct {
Key string // The role key/identifier
Name string // Human-readable role name
Description string // Optional description of the role
Permissions []string // List of permissions granted by this role
}
// GetAPIKeyScope retrieves the scope information for a Permit.io API key.
// This allows deriving the project and environment IDs from the API key itself,
// eliminating the need for separate PERMIT_IO_PROJECT_ID and PERMIT_IO_ENV_ID
// environment variables.
//
// Parameters:
// - client: HTTP client for making API requests
// - apiKey: The Permit.io API key
// - baseURL: The Permit.io API base URL (e.g., https://api.permit.io)
//
// Returns:
// - *APIKeyScope: The scope containing organization, project, and environment IDs
// - error: Error if the API call fails or response cannot be parsed
func GetAPIKeyScope(client *http.Client, apiKey, baseURL string) (*APIKeyScope, error) {
url := fmt.Sprintf("%s/v2/api-key/scope", baseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
var scope APIKeyScope
if err := json.Unmarshal(body, &scope); err != nil {
return nil, fmt.Errorf("error parsing response: %w", err)
}
return &scope, nil
}
// GetRoleDetails retrieves the details of a role from Permit.io, including its permissions.
// Uses the schema API endpoint to get role definitions.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Permit.io configuration containing API credentials and project details
// - roleKey: The role key to retrieve details for
//
// Returns:
// - *RoleDetails: The role details including permissions
// - error: Error if the retrieval fails
func GetRoleDetails(client *http.Client, config *PermitConfig, roleKey string) (*RoleDetails, error) {
projectID, err := config.GetProjectID(client)
if err != nil {
return nil, fmt.Errorf("failed to get project ID: %w", err)
}
envID, err := config.GetEnvID(client)
if err != nil {
return nil, fmt.Errorf("failed to get environment ID: %w", err)
}
url := fmt.Sprintf("%s/v2/schema/%s/%s/roles/%s",
config.BaseURL, projectID, envID, roleKey)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
var roleData struct {
Key string `json:"key"`
Name string `json:"name"`
Description string `json:"description"`
Permissions []string `json:"permissions"`
}
if err := json.Unmarshal(body, &roleData); err != nil {
return nil, fmt.Errorf("error parsing response: %w", err)
}
return &RoleDetails{
Key: roleData.Key,
Name: roleData.Name,
Description: roleData.Description,
Permissions: roleData.Permissions,
}, nil
}