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
876 lines
28 KiB
Go
876 lines
28 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"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
permitRoleAssignmentsPerPage = 100
|
|
maxPermitRoleAssignmentPages = 100
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
|
|
endpoint := 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", endpoint, 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)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s/v2/facts/%s/%s/users/%s", config.BaseURL, projectID, envID, userKey)
|
|
|
|
req, err := http.NewRequest("GET", endpoint, 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)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s/v2/facts/%s/%s/users/%s", config.BaseURL, projectID, envID, userKey)
|
|
|
|
req, err := http.NewRequest("DELETE", endpoint, 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
|
|
endpoint := fmt.Sprintf("%s/v2/facts/%s/%s/users?search=%s", config.BaseURL, projectID, envID, email)
|
|
|
|
req, err := http.NewRequest("GET", endpoint, 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)
|
|
}
|
|
|
|
endpoint := 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", endpoint, 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)
|
|
}
|
|
|
|
endpoint := 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", endpoint, 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)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments?user=%s",
|
|
config.BaseURL, projectID, envID, url.QueryEscape(userKey))
|
|
|
|
req, err := http.NewRequest("GET", endpoint, 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)
|
|
}
|
|
|
|
assignments, err := decodePermitRoleAssignments(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
roles := make([]string, 0, len(assignments))
|
|
for _, assignment := range assignments {
|
|
if assignment.Role != "" {
|
|
roles = append(roles, assignment.Role)
|
|
}
|
|
}
|
|
return roles, nil
|
|
}
|
|
|
|
// ListAllPermitRoleAssignments retrieves all role assignments in the configured Permit.io environment.
|
|
func ListAllPermitRoleAssignments(client *http.Client, config *PermitConfig) ([]RoleAssignment, 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)
|
|
}
|
|
|
|
assignments := []RoleAssignment{}
|
|
for page := 1; page <= maxPermitRoleAssignmentPages; page++ {
|
|
endpoint := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments?page=%d&per_page=%d",
|
|
config.BaseURL, projectID, envID, page, permitRoleAssignmentsPerPage)
|
|
|
|
req, err := http.NewRequest("GET", endpoint, 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)
|
|
}
|
|
|
|
body, readErr := io.ReadAll(resp.Body)
|
|
closeErr := resp.Body.Close()
|
|
if readErr != nil {
|
|
return nil, fmt.Errorf("error reading response: %w", readErr)
|
|
}
|
|
if closeErr != nil {
|
|
return nil, fmt.Errorf("error closing response body: %w", closeErr)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
pageAssignments, err := decodePermitRoleAssignments(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
assignments = append(assignments, pageAssignments...)
|
|
if len(pageAssignments) < permitRoleAssignmentsPerPage {
|
|
return assignments, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("Permit.io role assignment listing exceeded %d pages", maxPermitRoleAssignmentPages)
|
|
}
|
|
|
|
func decodePermitRoleAssignments(body []byte) ([]RoleAssignment, error) {
|
|
var directAssignments []RoleAssignment
|
|
if err := json.Unmarshal(body, &directAssignments); err == nil {
|
|
return directAssignments, nil
|
|
}
|
|
|
|
var response struct {
|
|
Data []RoleAssignment `json:"data"`
|
|
Items []RoleAssignment `json:"items"`
|
|
Results []RoleAssignment `json:"results"`
|
|
}
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
return nil, fmt.Errorf("error parsing response as object or array: %w", err)
|
|
}
|
|
switch {
|
|
case response.Data != nil:
|
|
return response.Data, nil
|
|
case response.Items != nil:
|
|
return response.Items, nil
|
|
case response.Results != nil:
|
|
return response.Results, nil
|
|
default:
|
|
return nil, fmt.Errorf("unexpected Permit.io role assignments response format")
|
|
}
|
|
}
|
|
|
|
// 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
|
|
endpoint := fmt.Sprintf("%s/v2/schema/%s/%s/roles", config.BaseURL, projectID, envID)
|
|
|
|
// Create HTTP request
|
|
req, err := http.NewRequest("GET", endpoint, 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) {
|
|
endpoint := fmt.Sprintf("%s/v2/api-key/scope", baseURL)
|
|
|
|
req, err := http.NewRequest("GET", endpoint, 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)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s/v2/schema/%s/%s/roles/%s",
|
|
config.BaseURL, projectID, envID, roleKey)
|
|
|
|
req, err := http.NewRequest("GET", endpoint, 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
|
|
}
|