6dccf494f8
cleanup permit policies and correct documentation * docs * policy cleanup * refactor * Merge remote-tracking branch 'origin/feature/permit-policy-cleanup' into feature/permit-policy-cleanup * docs and edits
679 lines
22 KiB
Go
679 lines
22 KiB
Go
// permit.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. It integrates with Cognito user data to sync
|
|
// user information between the two systems.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Permit.io types
|
|
type PermitUser struct {
|
|
Key string `json:"key"`
|
|
Email string `json:"email,omitempty"`
|
|
FirstName string `json:"first_name,omitempty"`
|
|
LastName string `json:"last_name,omitempty"`
|
|
Attributes map[string]interface{} `json:"attributes,omitempty"`
|
|
}
|
|
|
|
type RoleAssignment struct {
|
|
User string `json:"user"`
|
|
Role string `json:"role"`
|
|
Tenant string `json:"tenant"`
|
|
}
|
|
|
|
// APIKeyScope represents the scope information returned by the Permit.io API key introspection endpoint.
|
|
// It contains the organization, project, and environment IDs that the API key has access to.
|
|
// Organization-level keys will have only OrganizationID set.
|
|
// Project-level keys will have OrganizationID and ProjectID set.
|
|
// Environment-level keys will have all three fields set.
|
|
type APIKeyScope struct {
|
|
OrganizationID string `json:"organization_id"`
|
|
ProjectID string `json:"project_id,omitempty"`
|
|
EnvironmentID string `json:"environment_id,omitempty"`
|
|
}
|
|
|
|
// getAPIKeyScope retrieves the scope information for the provided Permit.io API key.
|
|
// This allows the tool to introspect a project-level or environment-level API key
|
|
// to automatically determine the project and environment IDs without requiring
|
|
// the user to specify them explicitly.
|
|
//
|
|
// Parameters:
|
|
// - client: HTTP client for making API requests
|
|
// - config: Application configuration containing the Permit.io API key and base URL
|
|
//
|
|
// Returns:
|
|
// - *APIKeyScope: The scope information containing organization, project, and environment IDs
|
|
// - error: Error if the API call fails or response cannot be parsed
|
|
func getAPIKeyScope(client *http.Client, config AppConfig) (*APIKeyScope, error) {
|
|
url := fmt.Sprintf("%s/v2/api-key/scope", config.PermitBaseURL)
|
|
|
|
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.PermitAPIKey)
|
|
|
|
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))
|
|
}
|
|
|
|
var scope APIKeyScope
|
|
if err := json.NewDecoder(resp.Body).Decode(&scope); err != nil {
|
|
return nil, fmt.Errorf("error decoding response: %w", err)
|
|
}
|
|
|
|
return &scope, nil
|
|
}
|
|
|
|
func createPermitUser(client *http.Client, config AppConfig, cognitoUser *CognitoUserResponse) error {
|
|
url := fmt.Sprintf("%s/v2/facts/%s/%s/users", config.PermitBaseURL, config.PermitProjectID, config.PermitEnvID)
|
|
|
|
userObj := PermitUser{
|
|
Key: cognitoUser.SubjectID, // Use Cognito Subject ID as the key
|
|
Email: cognitoUser.Email,
|
|
FirstName: cognitoUser.FirstName,
|
|
LastName: cognitoUser.LastName,
|
|
Attributes: map[string]interface{}{
|
|
"cognito_username": cognitoUser.Username,
|
|
"cognito_status": cognitoUser.Status,
|
|
},
|
|
}
|
|
|
|
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.PermitAPIKey)
|
|
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): %s\nTry running with '-project list' to see available projects and environments", 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 ErrorResponse
|
|
if err := json.Unmarshal(body, &errResp); err == nil && errResp.Error.Message != "" {
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, errResp.Error.Message)
|
|
}
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func assignRole(client *http.Client, config AppConfig, userID, role string) error {
|
|
url := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments", config.PermitBaseURL, config.PermitProjectID, config.PermitEnvID)
|
|
|
|
assignment := RoleAssignment{
|
|
User: userID,
|
|
Role: role,
|
|
Tenant: "default", // Using default 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.PermitAPIKey)
|
|
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 ErrorResponse
|
|
if err := json.Unmarshal(body, &errResp); err == nil && errResp.Error.Message != "" {
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, errResp.Error.Message)
|
|
}
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func findPermitUserByEmail(client *http.Client, config AppConfig, email string) (string, error) {
|
|
// Search for user by email
|
|
url := fmt.Sprintf("%s/v2/facts/%s/%s/users?search=%s", config.PermitBaseURL, config.PermitProjectID, config.PermitEnvID, 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.PermitAPIKey)
|
|
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
|
|
// First try to unmarshal as an object (paginated response)
|
|
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 {
|
|
// If all else fails, assume the response itself is an array
|
|
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
|
|
}
|
|
|
|
func deletePermitUser(client *http.Client, config AppConfig, userKey string) error {
|
|
url := fmt.Sprintf("%s/v2/facts/%s/%s/users/%s", config.PermitBaseURL, config.PermitProjectID, config.PermitEnvID, 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.PermitAPIKey)
|
|
|
|
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 ErrorResponse
|
|
if err := json.Unmarshal(body, &errResp); err == nil && errResp.Error.Message != "" {
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, errResp.Error.Message)
|
|
}
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getPermitUserRoles retrieves the roles assigned to a user in Permit.io
|
|
func getPermitUserRoles(client *http.Client, config AppConfig, userKey string) ([]string, error) {
|
|
url := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments?user=%s",
|
|
config.PermitBaseURL, config.PermitProjectID, config.PermitEnvID, 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.PermitAPIKey)
|
|
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)
|
|
}
|
|
|
|
var roles []string
|
|
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
|
|
}
|
|
|
|
// unassignRole removes a role assignment from a user in Permit.io
|
|
func unassignRole(client *http.Client, config AppConfig, userID, role string) error {
|
|
url := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments", config.PermitBaseURL, config.PermitProjectID, config.PermitEnvID)
|
|
|
|
assignment := RoleAssignment{
|
|
User: userID,
|
|
Role: role,
|
|
Tenant: "default", // Using default 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.PermitAPIKey)
|
|
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 ErrorResponse
|
|
if err := json.Unmarshal(body, &errResp); err == nil && errResp.Error.Message != "" {
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, errResp.Error.Message)
|
|
}
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// userExistsInPermit checks if a user exists in Permit.io by email
|
|
func userExistsInPermit(client *http.Client, config AppConfig, email string) (bool, string, error) {
|
|
userKey, err := findPermitUserByEmail(client, config, email)
|
|
if err != nil {
|
|
return false, "", err
|
|
}
|
|
return userKey != "", userKey, nil
|
|
}
|
|
|
|
func listProjectsAndEnvironments(config AppConfig) {
|
|
if config.PermitAPIKey == "" {
|
|
fmt.Fprintf(os.Stderr, "PERMIT_KEY environment variable is not set\n")
|
|
return
|
|
}
|
|
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
|
|
fmt.Println("Fetching available projects and environments...")
|
|
fmt.Println(strings.Repeat("=", 60))
|
|
|
|
// First, try to get the organization info
|
|
orgReq, _ := http.NewRequest("GET", config.PermitBaseURL+"/v2/orgs", nil)
|
|
orgReq.Header.Set("Authorization", "Bearer "+config.PermitAPIKey)
|
|
|
|
orgResp, err := client.Do(orgReq)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error fetching organization info: %v\n", err)
|
|
return
|
|
}
|
|
defer orgResp.Body.Close()
|
|
|
|
if orgResp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(orgResp.Body)
|
|
fmt.Fprintf(os.Stderr, "Error response from API: %s\n", string(body))
|
|
return
|
|
}
|
|
|
|
// Try to list projects
|
|
projReq, _ := http.NewRequest("GET", config.PermitBaseURL+"/v2/projects", nil)
|
|
projReq.Header.Set("Authorization", "Bearer "+config.PermitAPIKey)
|
|
|
|
projResp, err := client.Do(projReq)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error fetching projects: %v\n", err)
|
|
return
|
|
}
|
|
defer projResp.Body.Close()
|
|
|
|
if projResp.StatusCode == http.StatusOK {
|
|
var projects []map[string]interface{}
|
|
body, _ := io.ReadAll(projResp.Body)
|
|
if err := json.Unmarshal(body, &projects); err == nil {
|
|
fmt.Println("Available Projects:")
|
|
for _, proj := range projects {
|
|
fmt.Printf("\nProject Name: %v\n", proj["name"])
|
|
fmt.Printf("Project Key: %v\n", proj["key"])
|
|
fmt.Printf("Project ID: %v\n", proj["id"])
|
|
|
|
// For each project, try to get environments
|
|
if projID, ok := proj["id"].(string); ok {
|
|
envReq, _ := http.NewRequest("GET", fmt.Sprintf("%s/v2/projects/%s/envs", config.PermitBaseURL, projID), nil)
|
|
envReq.Header.Set("Authorization", "Bearer "+config.PermitAPIKey)
|
|
|
|
envResp, err := client.Do(envReq)
|
|
if err == nil && envResp.StatusCode == http.StatusOK {
|
|
var envs []map[string]interface{}
|
|
envBody, _ := io.ReadAll(envResp.Body)
|
|
if err := json.Unmarshal(envBody, &envs); err == nil {
|
|
fmt.Println(" Environments:")
|
|
for _, env := range envs {
|
|
fmt.Printf(" - Name: %v\n", env["name"])
|
|
fmt.Printf(" Key: %v\n", env["key"])
|
|
fmt.Printf(" ID: %v\n", env["id"])
|
|
}
|
|
}
|
|
envResp.Body.Close()
|
|
}
|
|
}
|
|
fmt.Println(strings.Repeat("-", 40))
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Println("Unable to list projects. Your API key might be environment-scoped.")
|
|
fmt.Println("\nTips for finding your project and environment IDs:")
|
|
fmt.Println("1. In the Permit.io dashboard, click on 'Copy Environment Key' - this often shows the context")
|
|
fmt.Println("2. Check the API Keys page in Settings - it may show the associated project/environment")
|
|
fmt.Println("3. Try using the project 'key' instead of 'name'")
|
|
fmt.Println("4. Contact Permit.io support for help identifying your project and environment IDs")
|
|
}
|
|
}
|
|
|
|
// ValidateCSVRoles validates that all roles specified in the CSV data exist in the target Permit.io environment.
|
|
// It takes the CSV user data, HTTP client, and configuration parameters, then checks if all requested roles
|
|
// are already defined in the Permit.io project/environment.
|
|
//
|
|
// Parameters:
|
|
// - client: HTTP client for making API requests
|
|
// - config: Application configuration containing Permit.io credentials and project details
|
|
// - csvUsers: Slice of user maps from CSV file, each containing user data and roles
|
|
//
|
|
// Returns:
|
|
// - []string: List of roles that are requested in the CSV. If error is nil, these are all valid roles.
|
|
// If error is not nil, these are the missing roles that need to be created.
|
|
// - error: nil if all roles exist, error describing missing roles if validation fails
|
|
func ValidateCSVRoles(client *http.Client, config AppConfig, csvUsers []map[string]string) ([]string, error) {
|
|
// Extract all unique roles requested in the CSV
|
|
requestedRoles := extractUniqueRolesFromCSV(csvUsers)
|
|
|
|
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)
|
|
}
|
|
|
|
// Check which requested roles are missing
|
|
missingRoles := findMissingRoles(requestedRoles, existingRoles)
|
|
|
|
if len(missingRoles) > 0 {
|
|
return missingRoles, fmt.Errorf("the following roles do not exist in Permit.io and must be created first: %s",
|
|
strings.Join(missingRoles, ", "))
|
|
}
|
|
|
|
// All roles exist
|
|
return requestedRoles, nil
|
|
}
|
|
|
|
// extractUniqueRolesFromCSV parses the CSV user data and extracts all unique roles
|
|
// mentioned across all users. It handles the comma-separated roles field and
|
|
// removes duplicates and empty values.
|
|
//
|
|
// Parameters:
|
|
// - csvUsers: Slice of user maps from CSV file
|
|
//
|
|
// Returns:
|
|
// - []string: Slice of unique role names found in the CSV data
|
|
func extractUniqueRolesFromCSV(csvUsers []map[string]string) []string {
|
|
roleSet := make(map[string]bool)
|
|
|
|
// Iterate through all users and collect their roles
|
|
for _, user := range csvUsers {
|
|
rolesStr, exists := user["roles"]
|
|
if !exists || rolesStr == "" {
|
|
continue
|
|
}
|
|
|
|
// Split comma-separated roles and add to set
|
|
roles := strings.Split(rolesStr, ",")
|
|
for _, role := range roles {
|
|
role = strings.TrimSpace(role)
|
|
if role != "" {
|
|
roleSet[role] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert set to slice
|
|
var uniqueRoles []string
|
|
for role := range roleSet {
|
|
uniqueRoles = append(uniqueRoles, role)
|
|
}
|
|
|
|
return uniqueRoles
|
|
}
|
|
|
|
// getExistingRoles retrieves all role definitions from the specified Permit.io project/environment.
|
|
// It makes an API call to the Permit.io roles endpoint and parses the response to extract role names.
|
|
//
|
|
// Parameters:
|
|
// - client: HTTP client for making API requests
|
|
// - config: Application configuration containing API credentials and project details
|
|
//
|
|
// Returns:
|
|
// - []string: Slice of role names that exist in the Permit.io environment
|
|
// - error: Error if API call fails or response cannot be parsed
|
|
func getExistingRoles(client *http.Client, config AppConfig) ([]string, error) {
|
|
// Construct API URL for roles endpoint
|
|
url := fmt.Sprintf("%s/v2/schema/%s/%s/roles", config.PermitBaseURL, config.PermitProjectID, config.PermitEnvID)
|
|
|
|
// 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.PermitAPIKey)
|
|
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
|
|
}
|
|
|
|
// findMissingRoles compares the requested roles against existing roles and returns
|
|
// a list of roles that are requested but do not exist in the target environment.
|
|
//
|
|
// Parameters:
|
|
// - requestedRoles: Slice of role names that are requested in the CSV
|
|
// - existingRoles: Slice of role names that exist in Permit.io
|
|
//
|
|
// Returns:
|
|
// - []string: Slice of role names that are missing (requested but don't exist)
|
|
func findMissingRoles(requestedRoles, existingRoles []string) []string {
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
return missingRoles
|
|
}
|