github.com/jfrog/jfrog-cli-core/v2@v2.52.0/utils/config/config_test.go (about)

     1  package config
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path/filepath"
     7  	"reflect"
     8  	"strconv"
     9  	"testing"
    10  
    11  	"github.com/google/uuid"
    12  	configtests "github.com/jfrog/jfrog-cli-core/v2/utils/config/tests"
    13  	"github.com/jfrog/jfrog-cli-core/v2/utils/tests"
    14  	testsutils "github.com/jfrog/jfrog-client-go/utils/tests"
    15  
    16  	"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
    17  	"github.com/jfrog/jfrog-cli-core/v2/utils/log"
    18  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    19  	"github.com/stretchr/testify/assert"
    20  )
    21  
    22  func init() {
    23  	log.SetDefaultLogger()
    24  }
    25  
    26  func TestCovertConfigV0ToV1(t *testing.T) {
    27  	configV0 := `
    28  		{
    29  		  "artifactory": {
    30  			  "url": "http://localhost:8080/artifactory/",
    31  			  "user": "user",
    32  			  "password": "password"
    33  		  }
    34  		}
    35  	`
    36  	content, err := convertConfigV0toV1([]byte(configV0))
    37  	assert.NoError(t, err)
    38  	configV1 := new(ConfigV4)
    39  	assert.NoError(t, json.Unmarshal(content, &configV1))
    40  	assertionV4Helper(t, configV1, 1, false)
    41  }
    42  
    43  func TestConvertConfigV0ToLatest(t *testing.T) {
    44  	configV0 := `
    45  		{
    46  		  "artifactory": {
    47  			  "url": "http://localhost:8080/artifactory/",
    48  			  "user": "user",
    49  			  "password": "password"
    50  		  },
    51  		  "missioncontrol": {
    52  			  "url": "http://localhost:8080/mc/"
    53  		  }
    54  		}
    55  	`
    56  
    57  	cleanUpTempEnv := configtests.CreateTempEnv(t, false)
    58  	defer cleanUpTempEnv()
    59  	content, err := convertIfNeeded([]byte(configV0))
    60  	assert.NoError(t, err)
    61  	configV6 := new(ConfigV6)
    62  	assert.NoError(t, json.Unmarshal(content, &configV6))
    63  	assertionHelper(t, configV6, 0)
    64  	assertCertsMigrationAndBackupCreation(t)
    65  }
    66  
    67  func TestConvertConfigV1ToLatest(t *testing.T) {
    68  	// The Artifactory username is uppercase intentionally,
    69  	// to test the lowercase conversion to version 3.
    70  	config := `
    71  		{
    72  		  "artifactory": [
    73  			{
    74  			  "url": "http://localhost:8080/artifactory/",
    75  			  "user": "USER",
    76  			  "password": "password",
    77  			  "serverId": "` + DefaultServerId + `",
    78  			  "isDefault": true
    79  			}
    80  		  ],
    81  		  "missioncontrol": {
    82  			"url": "http://localhost:8080/mc/"
    83  		  },
    84  		  "Version": "1"
    85  		}
    86  	`
    87  
    88  	cleanUpTempEnv := configtests.CreateTempEnv(t, false)
    89  	defer cleanUpTempEnv()
    90  	content, err := convertIfNeeded([]byte(config))
    91  	assert.NoError(t, err)
    92  	configV6 := new(ConfigV6)
    93  	assert.NoError(t, json.Unmarshal(content, &configV6))
    94  	assertionHelper(t, configV6, 1)
    95  
    96  	assert.Equal(t, "user", configV6.Servers[0].User, "The config conversion to version 3 is supposed to save the username as lowercase")
    97  
    98  	assertCertsMigrationAndBackupCreation(t)
    99  }
   100  
   101  func assertCertsMigrationAndBackupCreation(t *testing.T) {
   102  	assertCertsMigration(t)
   103  	backupDir, err := coreutils.GetJfrogBackupDir()
   104  	assert.NoError(t, err)
   105  	assert.DirExists(t, backupDir)
   106  }
   107  
   108  func TestConvertConfigV4ToLatest(t *testing.T) {
   109  	configV4 := `
   110  		{
   111  		  "artifactory": [
   112  			  {
   113  			  	"url": "http://localhost:8080/artifactory/",
   114  			 	"user": "user",
   115  				"password": "password",
   116  				"serverId": "` + DefaultServerId + `",
   117  				"isDefault": true
   118  			  }
   119  		  ],
   120  		  "missioncontrol": {
   121  			"url": "http://localhost:8080/mc/"
   122  		  },
   123  		  "version": "4"
   124  		}
   125  	`
   126  
   127  	cleanUpTempEnv := configtests.CreateTempEnv(t, false)
   128  	defer cleanUpTempEnv()
   129  	content, err := convertIfNeeded([]byte(configV4))
   130  	assert.NoError(t, err)
   131  	configV6 := new(ConfigV6)
   132  	assert.NoError(t, json.Unmarshal(content, &configV6))
   133  	assertionHelper(t, configV6, 4)
   134  }
   135  
   136  func TestConvertConfigV5ToV6(t *testing.T) {
   137  	configV5 := `
   138  		{
   139  		  "servers": [
   140  			      {
   141  					  "url": "http://localhost:8080/",
   142  					  "artifactoryUrl": "http://localhost:8080/artifactory/",
   143  					  "distributionUrl": "http://localhost:8080/distribution/",
   144  					  "xrayUrl": "http://localhost:8080/xray/",
   145  					  "missionControlUrl": "http://localhost:8080/mc/",
   146  					  "pipelinesUrl": "http://localhost:8080/pipelines/",
   147  					  "user": "user",
   148  			          "password": "password",
   149  					  "accessToken": "M9Zi1FY_lpA5dR01ev6EU6Tx_qRVsm2mSYWqobz",
   150  					  "RefreshToken": "a476324f-856c-41d7-b87e-3162e7d6jk91",
   151  					  "serverId": "Default-Server",
   152    					  "isDefault": true
   153  				  }
   154  		  ],
   155  		  "version": "5"
   156  		}
   157  	`
   158  
   159  	cleanUpTempEnv := configtests.CreateTempEnv(t, false)
   160  	defer cleanUpTempEnv()
   161  	content, err := convertIfNeeded([]byte(configV5))
   162  	assert.NoError(t, err)
   163  	configV6 := new(ConfigV6)
   164  	assert.NoError(t, json.Unmarshal(content, &configV6))
   165  	assertionHelper(t, configV6, 5)
   166  }
   167  
   168  func TestConfigEncryption(t *testing.T) {
   169  	// Config
   170  	cleanUpTempEnv := configtests.CreateTempEnv(t, true)
   171  	defer cleanUpTempEnv()
   172  
   173  	// Original decrypted config, read directly from file
   174  	originalConfig := readConfFromFile(t)
   175  
   176  	// Reading through this function will update encryption, and encrypt the config file.
   177  	readConfig, err := readConf()
   178  	assert.NoError(t, err)
   179  
   180  	// Config file encryption should be updated, so Enc=true. Secrets should be decrypted to be used in the rest of the execution.
   181  	verifyEncryptionStatus(t, originalConfig, readConfig, false)
   182  	// Config file should be encrypted.
   183  	encryptedConfig := readConfFromFile(t)
   184  	verifyEncryptionStatus(t, originalConfig, encryptedConfig, true)
   185  
   186  	// Verify successfully decrypting.
   187  	readConfig, err = readConf()
   188  	assert.NoError(t, err)
   189  	verifyEncryptionStatus(t, originalConfig, readConfig, false)
   190  }
   191  
   192  func TestConfigEncryptionEnvVar(t *testing.T) {
   193  	// Config
   194  	cleanUpTempEnv := configtests.CreateTempEnv(t, false)
   195  	defer cleanUpTempEnv()
   196  
   197  	// Set encryption key in JFROG_CLI_ENCRYPTION_KEY environment variable
   198  	key := uuid.NewString()[:32]
   199  	assert.NoError(t, os.Setenv(coreutils.EncryptionKey, key))
   200  	defer func() {
   201  		assert.NoError(t, os.Unsetenv(coreutils.EncryptionKey))
   202  	}()
   203  
   204  	// Save the config and ensure the secrets were updated
   205  	expectedConfig := createEncryptionTestConfig()
   206  	assert.NoError(t, saveConfig(expectedConfig))
   207  	actualConfig := readConfFromFile(t)
   208  	verifyEncryptionStatus(t, expectedConfig, actualConfig, true)
   209  
   210  	// Read config and ensure the server details are decrypted
   211  	actualConfig, err := readConf()
   212  	assert.NoError(t, err)
   213  	verifyEncryptionStatus(t, expectedConfig, actualConfig, false)
   214  }
   215  
   216  func TestConfigEncryptionEnvVarUpdate(t *testing.T) {
   217  	// Set testing environment
   218  	cleanUpJfrogHome, err := tests.SetJfrogHome()
   219  	assert.NoError(t, err)
   220  	defer cleanUpJfrogHome()
   221  
   222  	assert.NoError(t, saveConfig(createEncryptionTestConfig()))
   223  	expectedConfig := createEncryptionTestConfig()
   224  	actualConfig := readConfFromFile(t)
   225  
   226  	verifyEncryptionStatus(t, expectedConfig, actualConfig, false)
   227  
   228  	// Set encryption key in JFROG_CLI_ENCRYPTION_KEY environment variable
   229  	key := uuid.NewString()[:32]
   230  	assert.NoError(t, os.Setenv(coreutils.EncryptionKey, key))
   231  	defer func() {
   232  		assert.NoError(t, os.Unsetenv(coreutils.EncryptionKey))
   233  	}()
   234  
   235  	// Read config and ensure that the config was decrypted
   236  	actualConfig, err = readConf()
   237  	assert.NoError(t, err)
   238  	verifyEncryptionStatus(t, expectedConfig, actualConfig, false)
   239  }
   240  
   241  func createEncryptionTestConfig() *Config {
   242  	return &Config{ConfigV6{ConfigV5{
   243  		Version: strconv.Itoa(coreutils.GetCliConfigVersion()),
   244  		Servers: []*ServerDetails{{
   245  			ServerId:      "test-server",
   246  			Url:           "http://acme.jfrog.io",
   247  			User:          "elmar",
   248  			Password:      "Wabbit",
   249  			AccessToken:   "DewiciousWegOfWamb",
   250  			SshPassphrase: "KiwwTheWabbit",
   251  		}}},
   252  	}}
   253  }
   254  
   255  // Read config file "as-is" - without decryption
   256  func readConfFromFile(t *testing.T) *Config {
   257  	confFilePath, err := getConfFilePath()
   258  	assert.NoError(t, err)
   259  	config := new(Config)
   260  	assert.FileExists(t, confFilePath)
   261  	content, err := fileutils.ReadFile(confFilePath)
   262  	assert.NoError(t, err)
   263  	assert.NoError(t, json.Unmarshal(content, &config))
   264  	return config
   265  }
   266  
   267  func TestGetArtifactoriesFromConfig(t *testing.T) {
   268  	cleanUpJfrogHome, err := tests.SetJfrogHome()
   269  	assert.NoError(t, err)
   270  	defer cleanUpJfrogHome()
   271  
   272  	config := `
   273  		{
   274  		  "artifactory": [
   275  			{
   276  			  "url": "http://localhost:8080/artifactory/",
   277  			  "user": "user",
   278  			  "password": "password",
   279  			  "serverId": "name",
   280  			  "isDefault": true
   281  			},
   282  			{
   283  			  "url": "http://localhost:8080/artifactory/",
   284  			  "user": "user",
   285  			  "password": "password",
   286  			  "serverId": "notDefault"
   287  			}
   288  		  ],
   289  		  "version": "2"
   290  		}
   291  	`
   292  	content, err := convertIfNeeded([]byte(config))
   293  	assert.NoError(t, err)
   294  	latestConfig := new(Config)
   295  	assert.NoError(t, json.Unmarshal(content, &latestConfig))
   296  	serverDetails, err := GetDefaultConfiguredConf(latestConfig.Servers)
   297  	assert.NoError(t, err)
   298  	assert.Equal(t, serverDetails.ServerId, "name")
   299  
   300  	serverDetails, err = getServerConfByServerId("notDefault", latestConfig.Servers)
   301  	assert.NoError(t, err)
   302  	assert.Equal(t, serverDetails.ServerId, "notDefault")
   303  }
   304  
   305  func TestGetJfrogDependenciesPath(t *testing.T) {
   306  	// Check default value of dependencies path, should be JFROG_CLI_HOME_DIR/dependencies
   307  	dependenciesPath, err := GetJfrogDependenciesPath()
   308  	assert.NoError(t, err)
   309  	jfrogHomeDir, err := coreutils.GetJfrogHomeDir()
   310  	assert.NoError(t, err)
   311  	expectedDependenciesPath := filepath.Join(jfrogHomeDir, coreutils.JfrogDependenciesDirName)
   312  	assert.Equal(t, expectedDependenciesPath, dependenciesPath)
   313  
   314  	// Check dependencies' path when JFROG_DEPENDENCIES_DIR is set, should be JFROG_DEPENDENCIES_DIR/
   315  	previousDependenciesDirEnv := os.Getenv(coreutils.DependenciesDir)
   316  	expectedDependenciesPath = "/tmp/my-dependencies/"
   317  	testsutils.SetEnvAndAssert(t, coreutils.DependenciesDir, expectedDependenciesPath)
   318  	defer testsutils.SetEnvAndAssert(t, coreutils.DependenciesDir, previousDependenciesDirEnv)
   319  	dependenciesPath, err = GetJfrogDependenciesPath()
   320  	assert.NoError(t, err)
   321  	assert.Equal(t, expectedDependenciesPath, dependenciesPath)
   322  }
   323  
   324  func assertionV4Helper(t *testing.T, convertedConfig *ConfigV4, expectedVersion int, expectedEnc bool) {
   325  	assert.Equal(t, strconv.Itoa(expectedVersion), convertedConfig.Version)
   326  	assert.Equal(t, expectedEnc, convertedConfig.Enc)
   327  
   328  	rtConverted := convertedConfig.Artifactory
   329  	if rtConverted == nil {
   330  		assert.Fail(t, "empty Artifactory config!")
   331  		return
   332  	}
   333  	assert.Len(t, rtConverted, 1)
   334  	rtConfigType := reflect.TypeOf(rtConverted)
   335  	assert.Equal(t, "[]*config.ServerDetails", rtConfigType.String())
   336  	assert.True(t, rtConverted[0].IsDefault)
   337  	assert.Equal(t, DefaultServerId, rtConverted[0].ServerId)
   338  	assert.Equal(t, "http://localhost:8080/artifactory/", rtConverted[0].Url)
   339  	assert.Equal(t, "user", rtConverted[0].User)
   340  	assert.Equal(t, "password", rtConverted[0].Password)
   341  }
   342  
   343  func assertionHelper(t *testing.T, convertedConfig *ConfigV6, previousVersion int) {
   344  	assert.Equal(t, "6", convertedConfig.Version)
   345  
   346  	serversConverted := convertedConfig.Servers
   347  	if serversConverted == nil {
   348  		assert.Fail(t, "empty servers config!")
   349  		return
   350  	}
   351  	assert.Len(t, serversConverted, 1)
   352  	rtConfigType := reflect.TypeOf(serversConverted)
   353  	assert.Equal(t, "[]*config.ServerDetails", rtConfigType.String())
   354  	assert.True(t, serversConverted[0].IsDefault)
   355  	assert.Equal(t, DefaultServerId, serversConverted[0].ServerId)
   356  	assert.Equal(t, "http://localhost:8080/artifactory/", serversConverted[0].ArtifactoryUrl)
   357  	assert.Equal(t, "http://localhost:8080/mc/", serversConverted[0].MissionControlUrl)
   358  	assert.Equal(t, "user", serversConverted[0].User)
   359  	assert.Equal(t, "password", serversConverted[0].Password)
   360  	if previousVersion >= 5 {
   361  		assert.Equal(t, "http://localhost:8080/xray/", serversConverted[0].XrayUrl)
   362  		assert.Equal(t, "http://localhost:8080/distribution/", serversConverted[0].DistributionUrl)
   363  		assert.Equal(t, "M9Zi1FY_lpA5dR01ev6EU6Tx_qRVsm2mSYWqobz", serversConverted[0].AccessToken)
   364  		assert.Equal(t, "a476324f-856c-41d7-b87e-3162e7d6jk91", serversConverted[0].ArtifactoryRefreshToken)
   365  		assert.Equal(t, "", serversConverted[0].RefreshToken)
   366  	}
   367  }
   368  
   369  func TestHandleSecrets(t *testing.T) {
   370  	masterKey := "randomkeywithlengthofexactly32!!"
   371  
   372  	original := new(Config)
   373  	original.Servers = []*ServerDetails{{User: "user", Password: "password", Url: "http://localhost:8080/artifactory/", AccessToken: "accessToken",
   374  		RefreshToken: "refreshToken", SshPassphrase: "sshPass"}}
   375  	original.Enc = true
   376  
   377  	newConf, err := original.Clone()
   378  	assert.NoError(t, err)
   379  
   380  	// Encrypt decrypted
   381  	assert.NoError(t, handleSecrets(original, encrypt, masterKey))
   382  	verifyEncryptionStatus(t, original, newConf, true)
   383  
   384  	// Decrypt encrypted
   385  	assert.NoError(t, handleSecrets(original, decrypt, masterKey))
   386  	newConf.Enc = false
   387  	verifyEncryptionStatus(t, original, newConf, false)
   388  }
   389  
   390  func verifyEncryptionStatus(t *testing.T, original, actual *Config, encryptionExpected bool) {
   391  	assert.Equal(t, len(original.Servers), len(actual.Servers))
   392  	var equals []bool
   393  	for i := range actual.Servers {
   394  		if original.Servers[i].Password != "" {
   395  			equals = append(equals, original.Servers[i].Password == actual.Servers[i].Password)
   396  		}
   397  		if original.Servers[i].AccessToken != "" {
   398  			equals = append(equals, original.Servers[i].AccessToken == actual.Servers[i].AccessToken)
   399  		}
   400  		if original.Servers[i].RefreshToken != "" {
   401  			equals = append(equals, original.Servers[i].RefreshToken == actual.Servers[i].RefreshToken)
   402  		}
   403  		assert.Equal(t, encryptionExpected, actual.Enc)
   404  	}
   405  
   406  	if encryptionExpected {
   407  		// Verify non match.
   408  		assert.Zero(t, coreutils.SumTrueValues(equals))
   409  	} else {
   410  		// Verify all match.
   411  		assert.Equal(t, coreutils.SumTrueValues(equals), len(equals))
   412  	}
   413  }
   414  
   415  func assertCertsMigration(t *testing.T) {
   416  	certsDir, err := coreutils.GetJfrogCertsDir()
   417  	assert.NoError(t, err)
   418  	assert.DirExists(t, certsDir)
   419  	files, err := os.ReadDir(certsDir)
   420  	assert.NoError(t, err)
   421  	// Verify only the certs were moved
   422  	assert.Len(t, files, 2)
   423  }