github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/pkg/sandbox/keycloak.go (about) 1 package sandbox 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "net/url" 11 "strings" 12 "time" 13 14 . "github.com/onsi/ginkgo/v2" 15 "github.com/redhat-appstudio/e2e-tests/pkg/utils" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/klog" 18 ) 19 20 const ( 21 DEFAULT_KEYCLOAK_INSTANCE_NAME = "keycloak" 22 DEFAULT_KEYCLOAK_NAMESPACE = "dev-sso" 23 ) 24 25 type KeycloakAuth struct { 26 // An access token is a token delivered by they keycloak server, and which allows an application to access to a resource 27 AccessToken string `json:"access_token"` 28 29 //refresh token is subject to SSO Session Idle timeout (30mn -default) and SSO Session Max lifespan (10hours-default) whereas offline token never expires 30 RefreshToken string `json:"refresh_token"` 31 } 32 33 // GetKeycloakToken return a token for admins 34 func (k *SandboxController) GetKeycloakToken(clientID string, userName string, password string, realm string) (keycloakAuth *KeycloakAuth, err error) { 35 data := url.Values{ 36 "client_id": {clientID}, 37 "username": {userName}, 38 "password": {password}, 39 "grant_type": {"password"}, 40 } 41 42 request, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/auth/realms/%s/protocol/openid-connect/token", k.KeycloakUrl, realm), strings.NewReader(data.Encode())) 43 44 if err != nil { 45 klog.Errorf("failed to get token from keycloak: %v", err) 46 return nil, err 47 } 48 request.Header.Add("Content-Type", "application/x-www-form-urlencoded") 49 50 response, err := k.HttpClient.Do(request) 51 52 if err != nil || response.StatusCode != 200 { 53 var statusCode string 54 if response == nil { 55 statusCode = "nil" 56 } else { 57 statusCode = fmt.Sprintf("%d", response.StatusCode) 58 } 59 return nil, fmt.Errorf("failed to get keycloak token, realm: %s, userName: %s, client-id: %s statusCode: %s, url: %s", realm, userName, clientID, statusCode, k.KeycloakUrl) 60 } 61 defer response.Body.Close() 62 63 err = json.NewDecoder(response.Body).Decode(&keycloakAuth) 64 65 return keycloakAuth, err 66 } 67 68 /* 69 RegisterKeycloakUser create a username in keycloak service and return if succeed or not 70 curl --location --request POST 'https://<keycloak-route>/auth/admin/realms/testrealm/users' \ 71 --header 'Content-Type: application/json' \ 72 --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJyS2VyZnczU2tzV2hBUF9TeUJuMDRaRm5Pa09ITVFRRmpnOGhjaG12X3VVIn0.eyJleHAiOjE2NzQ3NTkyODksImlhdCI6MTY3NDc1OTIyOSwianRpIjoiY2ZjNzNjMjEtZDU5Mi00MmI4LTk4MWMtYjc5MjAxNzI3OTJhIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay1kZXYtc3NvLmFwcHMuY2x1c3Rlci05cm05ei45cm05ei5zYW5kYm94MTE3OS5vcGVudGxjLmNvbS9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiI4ODcxMmJjOS1kZDBiLTQxNTktOGE1Ny1mZTRjMDlmYzBhM2IiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiM2I3MDM5NmItMzk4Yy00MjQyLTljZDMtZGJlYjM0ZWVjYmE0IiwiYWNyIjoiMSIsInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6IjNiNzAzOTZiLTM5OGMtNDI0Mi05Y2QzLWRiZWIzNGVlY2JhNCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4ifQ.GBHKQC0VZk4nEWVXDYC-Npk5Z503xlkDNbcrgd9nRTWcLZdD6HmgKnvGgoVYBssiSQyBYnAAqVQLGslbENjtohOlU4UxV0-Tsr2OpJUlKP0oMBVcna745UHAxU2JcVraVR4UkiryZbAOTJyUYKdhszqmfkGWPukTAo4lB2GO7HdfyU1UAwp8mzfLQ6WWV-LmeFjUUpwGOUed3Ztoa4DMBnVNFp7WHqoFyPO6xSTqq59ai__bJ8_8W7KfUTI6Rmfcno-6_9PtWFC8_bvs8bRBV7Xs8j4wn-7Y2-f9WTGC8EfUTacVGTf1ma-lBUEzWKodc7XH_5O18Huko3eS3RMDTA' \ 73 74 --data-raw "{ 75 "firstName":"user1", 76 "lastName":"user1", 77 "username":"user1", 78 "enabled":"true", 79 "email":"user1@test.com", 80 "credentials":[ 81 { 82 "type":"password", 83 "value":"user1", 84 "temporary":"false" 85 } 86 ] 87 }" 88 */ 89 func (k *SandboxController) RegisterKeycloakUser(userName string, keycloakToken string, realm string) (user *KeycloakUser, err error) { 90 user = k.getKeycloakUserSpec(userName) 91 payload, err := json.Marshal(user) 92 if err != nil { 93 return user, err 94 } 95 96 req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/auth/admin/realms/%s/users", k.KeycloakUrl, realm), bytes.NewReader(payload)) 97 if err != nil { 98 return user, err 99 } 100 req.Header.Set("Content-Type", "application/json") 101 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", keycloakToken)) 102 103 resp, err := k.HttpClient.Do(req) 104 if err != nil || resp.StatusCode != 201 { 105 var statusCode string 106 if resp == nil { 107 statusCode = "nil" 108 } else { 109 statusCode = fmt.Sprintf("%d", resp.StatusCode) 110 } 111 return user, fmt.Errorf("failed to create keycloak users. Status code %s", statusCode) 112 } 113 defer resp.Body.Close() 114 115 return user, err 116 } 117 118 // Return specs for a given user 119 func (k *SandboxController) getKeycloakUserSpec(userName string) *KeycloakUser { 120 return &KeycloakUser{ 121 FirstName: userName, 122 LastName: userName, 123 Username: userName, 124 Enabled: "true", 125 Email: fmt.Sprintf("%s@test.com", userName), 126 Credentials: []KeycloakUserCredentials{ 127 { 128 Type: "password", 129 Value: userName, 130 Temporary: "false", 131 }, 132 }, 133 } 134 } 135 136 // Add a valid description 137 func (s *SandboxController) IsKeycloakRunning() error { 138 return utils.WaitUntil(func() (done bool, err error) { 139 sets, err := s.KubeClient.AppsV1().StatefulSets(DEFAULT_KEYCLOAK_NAMESPACE).Get(context.Background(), DEFAULT_KEYCLOAK_INSTANCE_NAME, metav1.GetOptions{}) 140 141 if err != nil { 142 return false, fmt.Errorf("keycloak instance is not ready. Please check keycloak deployment: %+v", err) 143 } 144 145 if sets.Status.ReadyReplicas == *sets.Spec.Replicas { 146 return true, nil 147 } 148 klog.Info("keycloak instance is not ready. Please check keycloak deployment") 149 150 return false, nil 151 }, 5*time.Minute) 152 } 153 154 // Add a valid description 155 func (s *SandboxController) GetKeycloakAdminSecret() (adminPassword string, err error) { 156 keycloakAdminSecret, err := s.KubeClient.CoreV1().Secrets(DEFAULT_KEYCLOAK_NAMESPACE).Get(context.Background(), DEFAULT_KEYCLOAK_ADMIN_SECRET, metav1.GetOptions{}) 157 158 if err != nil { 159 return "", fmt.Errorf("failed to fetch keycloak secret from namespace: %s, secretName: %s", DEFAULT_KEYCLOAK_NAMESPACE, DEFAULT_KEYCLOAK_ADMIN_SECRET) 160 } 161 162 adminPassword = string(keycloakAdminSecret.Data[SECRET_KEY]) 163 if adminPassword == "" { 164 return "", fmt.Errorf("admin password dont exist in secret %s", DEFAULT_KEYCLOAK_ADMIN_SECRET) 165 } 166 167 return adminPassword, nil 168 } 169 170 func (s *SandboxController) KeycloakUserExists(realm string, token string, username string) bool { 171 ///{realm}/users?username=toto 172 ///admin/realms/{my-realm}/users?search={username} 173 request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/auth/admin/realms/%s/users?username=%s", s.KeycloakUrl, realm, username), strings.NewReader("")) 174 if err != nil { 175 GinkgoWriter.Printf("failed to create an HTTP request in order to get a keycloak user: %+v\n", err) 176 return false 177 } 178 request.Header.Add("Content-Type", "application/x-www-form-urlencoded") 179 request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) 180 181 response, err := s.HttpClient.Do(request) 182 183 if err != nil { 184 GinkgoWriter.Printf("failed when searching for a keycloak user: %+v\n", err) 185 return false 186 } 187 defer response.Body.Close() 188 189 body, _ := io.ReadAll(response.Body) 190 if err != nil { 191 GinkgoWriter.Printf("failed to read a response body from keycloak server: %+v\n", err) 192 return false 193 } 194 // Keycloak API server returns status code 200 even if no user is found, thus we need to parse the response body 195 // https://www.keycloak.org/docs-api/18.0/rest-api/#_users_resource 196 var users []any 197 err = json.Unmarshal(body, &users) 198 if err != nil { 199 GinkgoWriter.Printf("failed when unmarshalling response body: %+v\n", err) 200 } 201 202 return len(users) > 0 203 }