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 }