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  }