Last active
April 8, 2025 22:41
-
-
Save Noxsios/2901c752a371f9d48493cd879c41e9c4 to your computer and use it in GitHub Desktop.
create doug user
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This example is a naive translation of the shell task into Go, | |
// there are many optimizations + reuse needed before this would be fully accepted as a builtin | |
// + unit tests | |
// BuiltinSetupDougUser sets up a doug user in Keycloak | |
type BuiltinSetupDougUser struct { | |
KeycloakGroup string `json:"keycloak_group,omitempty" jsonschema:"description=Optional Keycloak group to add the user to"` | |
} | |
func (b BuiltinSetupDougUser) Execute(ctx context.Context) error { | |
logger := log.FromContext(ctx) | |
// Get Keycloak admin password | |
const namespace = "keycloak" | |
const secretName = "keycloak-admin-password" | |
const passwordKey = "password" | |
config, err := rest.InClusterConfig() | |
if err != nil { | |
return fmt.Errorf("error creating Kubernetes config: %w", err) | |
} | |
clientset, err := kubernetes.NewForConfig(config) | |
if err != nil { | |
return fmt.Errorf("error creating Kubernetes client: %w", err) | |
} | |
var secret *corev1.Secret | |
secret, err = clientset.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) | |
if err != nil { | |
return fmt.Errorf("error getting secret %s in namespace %s: %w", secretName, namespace, err) | |
} | |
encodedPassword, ok := secret.Data[passwordKey] | |
if !ok { | |
return fmt.Errorf("key %s not found in secret %s in namespace %s", passwordKey, secretName, namespace) | |
} | |
keycloakAdminPassword, err := base64.StdEncoding.DecodeString(string(encodedPassword)) | |
if err != nil { | |
return fmt.Errorf("error decoding password: %w", err) | |
} | |
// Get Keycloak admin token | |
keycloakURL := "https://keycloak.admin.uds.dev" | |
tokenURL := fmt.Sprintf("%s/realms/master/protocol/openid-connect/token", keycloakURL) | |
data := url.Values{} | |
data.Set("username", "admin") | |
data.Set("password", string(keycloakAdminPassword)) | |
data.Set("client_id", "admin-cli") | |
data.Set("grant_type", "password") | |
req, err := http.NewRequestWithContext(ctx, "POST", tokenURL, strings.NewReader(data.Encode())) | |
if err != nil { | |
return fmt.Errorf("error creating token request: %w", err) | |
} | |
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") | |
client := &http.Client{Timeout: 10 * time.Second} | |
resp, err := client.Do(req) | |
if err != nil { | |
return fmt.Errorf("error getting admin token: %w", err) | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != http.StatusOK { | |
body, _ := io.ReadAll(resp.Body) | |
return fmt.Errorf("error getting admin token, status: %s, body: %s", resp.Status, string(body)) | |
} | |
var tokenResponse struct { | |
AccessToken string `json:"access_token"` | |
} | |
if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil { | |
return fmt.Errorf("error decoding token response: %w", err) | |
} | |
keycloakAdminToken := tokenResponse.AccessToken | |
logger.Info("Got Keycloak admin token") | |
// Create the doug user in the UDS Realm | |
userURL := fmt.Sprintf("%s/admin/realms/uds/users", keycloakURL) | |
userPayload := map[string]interface{}{ | |
"username": "doug", | |
"firstName": "Doug", | |
"lastName": "Unicorn", | |
"email": "[email protected]", | |
"attributes": map[string][]string{"mattermostid": {"1"}}, | |
"emailVerified": true, | |
"enabled": true, | |
"requiredActions": []string{}, | |
"credentials": []map[string]interface{}{ | |
{ | |
"type": "password", | |
"value": "unicorn123!@#UN", | |
"temporary": false, | |
}, | |
}, | |
} | |
// Add group if specified | |
if b.KeycloakGroup != "" { | |
userPayload["groups"] = []string{b.KeycloakGroup} | |
} | |
userJSON, err := json.Marshal(userPayload) | |
if err != nil { | |
return fmt.Errorf("error marshaling user JSON: %w", err) | |
} | |
req, err = http.NewRequestWithContext(ctx, "POST", userURL, bytes.NewBuffer(userJSON)) | |
if err != nil { | |
return fmt.Errorf("error creating user request: %w", err) | |
} | |
req.Header.Add("Content-Type", "application/json") | |
req.Header.Add("Authorization", "Bearer "+keycloakAdminToken) | |
resp, err = client.Do(req) | |
if err != nil { | |
return fmt.Errorf("error creating user: %w", err) | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { | |
body, _ := io.ReadAll(resp.Body) | |
return fmt.Errorf("error creating user, status: %s, body: %s", resp.Status, string(body)) | |
} | |
logger.Info("Created user doug") | |
// Disable 2FA | |
flowsURL := fmt.Sprintf("%s/admin/realms/uds/authentication/flows/Authentication/executions", keycloakURL) | |
req, err = http.NewRequestWithContext(ctx, "GET", flowsURL, nil) | |
if err != nil { | |
return fmt.Errorf("error creating flows request: %w", err) | |
} | |
req.Header.Add("Authorization", "Bearer "+keycloakAdminToken) | |
resp, err = client.Do(req) | |
if err != nil { | |
return fmt.Errorf("error getting flows: %w", err) | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != http.StatusOK { | |
body, _ := io.ReadAll(resp.Body) | |
return fmt.Errorf("error getting flows, status: %s, body: %s", resp.Status, string(body)) | |
} | |
var flows []map[string]interface{} | |
if err := json.NewDecoder(resp.Body).Decode(&flows); err != nil { | |
return fmt.Errorf("error decoding flows response: %w", err) | |
} | |
var conditionalOTPID string | |
for _, flow := range flows { | |
if displayName, ok := flow["displayName"].(string); ok && displayName == "Conditional OTP" { | |
if id, ok := flow["id"].(string); ok { | |
conditionalOTPID = id | |
break | |
} | |
} | |
} | |
if conditionalOTPID == "" { | |
return fmt.Errorf("could not find Conditional OTP flow") | |
} | |
// Update the flow to disable 2FA | |
flowUpdateURL := fmt.Sprintf("%s/admin/realms/uds/authentication/flows/Authentication/executions", keycloakURL) | |
flowUpdatePayload := map[string]string{ | |
"id": conditionalOTPID, | |
"requirement": "DISABLED", | |
} | |
flowUpdateJSON, err := json.Marshal(flowUpdatePayload) | |
if err != nil { | |
return fmt.Errorf("error marshaling flow update JSON: %w", err) | |
} | |
req, err = http.NewRequestWithContext(ctx, "PUT", flowUpdateURL, bytes.NewBuffer(flowUpdateJSON)) | |
if err != nil { | |
return fmt.Errorf("error creating flow update request: %w", err) | |
} | |
req.Header.Add("Content-Type", "application/json") | |
req.Header.Add("Authorization", "Bearer "+keycloakAdminToken) | |
resp, err = client.Do(req) | |
if err != nil { | |
return fmt.Errorf("error updating flow: %w", err) | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { | |
body, _ := io.ReadAll(resp.Body) | |
return fmt.Errorf("error updating flow, status: %s, body: %s", resp.Status, string(body)) | |
} | |
logger.Info("Disabled 2FA") | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment