github.com/jfrog/jfrog-cli-core/v2@v2.51.0/common/commands/config_test.go (about) 1 package commands 2 3 import ( 4 "encoding/json" 5 "os" 6 "testing" 7 8 "github.com/jfrog/jfrog-cli-core/v2/common/tests" 9 utilsTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" 10 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 11 12 "github.com/jfrog/jfrog-cli-core/v2/utils/config" 13 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 14 "github.com/jfrog/jfrog-cli-core/v2/utils/log" 15 "github.com/stretchr/testify/assert" 16 ) 17 18 const testServerId = "test" 19 20 func init() { 21 log.SetDefaultLogger() 22 } 23 24 func TestBasicAuth(t *testing.T) { 25 inputDetails := tests.CreateTestServerDetails() 26 inputDetails.User = "admin" 27 inputDetails.Password = "password" 28 29 configAndTest(t, inputDetails, false) 30 configAndTest(t, inputDetails, true) 31 } 32 33 func TestUsernameSavedLowercase(t *testing.T) { 34 inputDetails := tests.CreateTestServerDetails() 35 inputDetails.User = "ADMIN" 36 inputDetails.Password = "password" 37 38 outputConfig, err := configAndGetTestServer(t, inputDetails, false, false) 39 assert.NoError(t, err) 40 assert.Equal(t, outputConfig.User, "admin", "The config command is supposed to save username as lowercase") 41 } 42 43 func TestDefaultServerId(t *testing.T) { 44 inputDetails := tests.CreateTestServerDetails() 45 inputDetails.User = "admin" 46 inputDetails.Password = "password" 47 // Remove server ID to verify the default one will be applied in non-interactive execution. 48 inputDetails.ServerId = "" 49 50 doConfig(t, "", inputDetails, false, true, false) 51 outputConfig, err := GetConfig(config.DefaultServerId, false) 52 assert.NoError(t, err) 53 assert.Equal(t, config.DefaultServerId, outputConfig.ServerId) 54 assert.NoError(t, NewConfigCommand(Delete, config.DefaultServerId).Run()) 55 } 56 57 func TestArtifactorySshKey(t *testing.T) { 58 inputDetails := tests.CreateTestServerDetails() 59 inputDetails.SshKeyPath = "/tmp/sshKey" 60 inputDetails.SshPassphrase = "123456" 61 inputDetails.ArtifactoryUrl = "ssh://localhost:1339/" 62 63 configAndTest(t, inputDetails, false) 64 configAndTest(t, inputDetails, true) 65 } 66 67 func TestAccessToken(t *testing.T) { 68 inputDetails := tests.CreateTestServerDetails() 69 inputDetails.AccessToken = "accessToken" 70 71 configAndTest(t, inputDetails, false) 72 configAndTest(t, inputDetails, true) 73 } 74 75 func TestAccessTokenWithUsername(t *testing.T) { 76 inputDetails := tests.CreateTestServerDetails() 77 inputDetails.AccessToken = "accessToken" 78 inputDetails.User = "ADMIN" 79 80 configAndTest(t, inputDetails, false) 81 configAndTest(t, inputDetails, true) 82 } 83 84 func TestApiKeyInAccessToken(t *testing.T) { 85 inputDetails := tests.CreateTestServerDetails() 86 apiKey := "AKCp8" + "fsafsadfkljaodjpioqwu4-32742398ujklwertjp89347583jtklsdfmgklsdjuftp397859jsdklfnsljgflkdsjlgjld" 87 inputDetails.AccessToken = apiKey 88 89 // Should throw error if access token is API key and no username 90 configCmd := NewConfigCommand(AddOrEdit, testServerId).SetDetails(inputDetails).SetUseBasicAuthOnly(true).SetInteractive(false) 91 configCmd.disablePrompts = true 92 assert.ErrorContains(t, configCmd.Run(), "the provided Access Token is an API key") 93 94 // Should work without error if access token is API key but username exists 95 inputDetails.User = "ADMIN" 96 configCmd.SetDetails(inputDetails) 97 assert.NoError(t, configCmd.Run()) 98 } 99 100 func TestMTLS(t *testing.T) { 101 inputDetails := tests.CreateTestServerDetails() 102 inputDetails.ClientCertPath = "test/cert/path" 103 inputDetails.ClientCertKeyPath = "test/cert/key/path" 104 105 configAndTest(t, inputDetails, false) 106 configAndTest(t, inputDetails, true) 107 } 108 109 func TestArtifactoryRefreshToken(t *testing.T) { 110 // Import after tokens were generated. 111 inputDetails := tests.CreateTestServerDetails() 112 inputDetails.User = "admin" 113 inputDetails.Password = "password" 114 inputDetails.AccessToken = "accessToken" 115 inputDetails.ArtifactoryRefreshToken = "refreshToken" 116 117 configAndTest(t, inputDetails, false) 118 configAndTest(t, inputDetails, true) 119 120 // Import before tokens were generated. 121 inputDetails.AccessToken = "" 122 inputDetails.ArtifactoryRefreshToken = "" 123 configAndTest(t, inputDetails, false) 124 configAndTest(t, inputDetails, true) 125 } 126 127 func TestEmptyCredentials(t *testing.T) { 128 configAndTest(t, tests.CreateTestServerDetails(), false) 129 } 130 131 func TestUrls(t *testing.T) { 132 t.Run("non-interactive", func(t *testing.T) { testUrls(t, false) }) 133 t.Run("interactive", func(t *testing.T) { testUrls(t, true) }) 134 } 135 136 func testUrls(t *testing.T, interactive bool) { 137 inputDetails := config.ServerDetails{ 138 Url: "http://localhost:8080", User: "admin", Password: "password", 139 ServerId: testServerId, ClientCertPath: "test/cert/path", ClientCertKeyPath: "test/cert/key/path", 140 IsDefault: false} 141 142 outputConfig, err := configAndGetTestServer(t, &inputDetails, false, interactive) 143 assert.NoError(t, err) 144 145 assert.Equal(t, "http://localhost:8080/", outputConfig.GetUrl()) 146 assert.Equal(t, "http://localhost:8080/artifactory/", outputConfig.GetArtifactoryUrl()) 147 assert.Equal(t, "http://localhost:8080/distribution/", outputConfig.GetDistributionUrl()) 148 assert.Equal(t, "http://localhost:8080/xray/", outputConfig.GetXrayUrl()) 149 assert.Equal(t, "http://localhost:8080/mc/", outputConfig.GetMissionControlUrl()) 150 assert.Equal(t, "http://localhost:8080/pipelines/", outputConfig.GetPipelinesUrl()) 151 152 inputDetails.ArtifactoryUrl = "http://localhost:8081/artifactory" 153 inputDetails.DistributionUrl = "http://localhost:8081/distribution" 154 inputDetails.XrayUrl = "http://localhost:8081/xray" 155 inputDetails.MissionControlUrl = "http://localhost:8081/mc" 156 inputDetails.PipelinesUrl = "http://localhost:8081/pipelines" 157 inputDetails.AccessUrl = "http://localhost:8081/access" 158 159 outputConfig, err = configAndGetTestServer(t, &inputDetails, false, interactive) 160 assert.NoError(t, err) 161 162 assert.Equal(t, "http://localhost:8080/", outputConfig.GetUrl()) 163 assert.Equal(t, "http://localhost:8081/artifactory/", outputConfig.GetArtifactoryUrl()) 164 assert.Equal(t, "http://localhost:8081/distribution/", outputConfig.GetDistributionUrl()) 165 assert.Equal(t, "http://localhost:8081/xray/", outputConfig.GetXrayUrl()) 166 assert.Equal(t, "http://localhost:8081/mc/", outputConfig.GetMissionControlUrl()) 167 assert.Equal(t, "http://localhost:8081/pipelines/", outputConfig.GetPipelinesUrl()) 168 } 169 170 func TestBasicAuthOnlyOption(t *testing.T) { 171 inputDetails := tests.CreateTestServerDetails() 172 inputDetails.User = "admin" 173 inputDetails.Password = "password" 174 175 // Verify setting the option disables refreshable tokens. 176 outputConfig, err := configAndGetTestServer(t, inputDetails, true, false) 177 assert.NoError(t, err) 178 assert.Equal(t, coreutils.TokenRefreshDisabled, outputConfig.ArtifactoryTokenRefreshInterval, "expected refreshable token to be disabled") 179 assert.NoError(t, NewConfigCommand(Delete, testServerId).Run()) 180 181 // Verify setting the option enables refreshable tokens. 182 outputConfig, err = configAndGetTestServer(t, inputDetails, false, false) 183 assert.NoError(t, err) 184 assert.Equal(t, coreutils.TokenRefreshDefaultInterval, outputConfig.ArtifactoryTokenRefreshInterval, "expected refreshable token to be enabled") 185 assert.NoError(t, NewConfigCommand(Delete, testServerId).Run()) 186 } 187 188 func TestMakeDefaultOption(t *testing.T) { 189 cleanUpJfrogHome, err := utilsTests.SetJfrogHome() 190 assert.NoError(t, err) 191 defer cleanUpJfrogHome() 192 193 originalDefault := tests.CreateTestServerDetails() 194 originalDefault.ServerId = "originalDefault" 195 originalDefault.IsDefault = false 196 newDefault := tests.CreateTestServerDetails() 197 newDefault.ServerId = "newDefault" 198 newDefault.IsDefault = false 199 200 // Config the first server, and expect it to be default because it is the only server. 201 configAndAssertDefault(t, originalDefault, false) 202 defer deleteServer(t, originalDefault.ServerId) 203 204 // Config a second server and pass the makeDefault option. 205 configAndAssertDefault(t, newDefault, true) 206 defer deleteServer(t, newDefault.ServerId) 207 } 208 209 func configAndAssertDefault(t *testing.T, inputDetails *config.ServerDetails, makeDefault bool) { 210 outputConfig, err := configAndGetServer(t, inputDetails.ServerId, inputDetails, false, false, makeDefault) 211 assert.NoError(t, err) 212 assert.Equal(t, inputDetails.ServerId, outputConfig.ServerId) 213 assert.True(t, outputConfig.IsDefault) 214 } 215 216 func deleteServer(t *testing.T, serverId string) { 217 assert.NoError(t, NewConfigCommand(Delete, serverId).Run()) 218 } 219 220 type unsafeUrlTest struct { 221 url string 222 isSafe bool 223 } 224 225 var unsafeUrlTestCases = []unsafeUrlTest{ 226 // Safe URLs 227 {"https://acme.jfrog.io", true}, 228 {"http://127.0.0.1", true}, 229 {"http://localhost", true}, 230 {"http://127.0.0.1:8081", true}, 231 {"http://localhost:8081", true}, 232 {"ssh://localhost:1339/", true}, 233 234 // Unsafe URLs: 235 {"http://acme.jfrog.io", false}, 236 {"http://acme.jfrog.io:8081", false}, 237 {"http://localhost-123", false}, 238 } 239 240 func TestAssertUrlsSafe(t *testing.T) { 241 for _, testCase := range unsafeUrlTestCases { 242 t.Run(testCase.url, func(t *testing.T) { 243 // Test non-interactive - should pass with a warning message 244 inputDetails := &config.ServerDetails{Url: testCase.url, ServerId: testServerId} 245 configAndTest(t, inputDetails, false) 246 247 // Test interactive - should fail with an error 248 configCmd := NewConfigCommand(AddOrEdit, testServerId).SetDetails(inputDetails).SetInteractive(true) 249 configCmd.disablePrompts = true 250 err := configCmd.Run() 251 if testCase.isSafe { 252 assert.NoError(t, err) 253 } else { 254 assert.ErrorContains(t, err, "config was aborted due to an insecure HTTP connection") 255 } 256 }) 257 } 258 } 259 260 func TestExportEmptyConfig(t *testing.T) { 261 cliHome, exist := os.LookupEnv(coreutils.HomeDir) 262 defer func() { 263 if exist { 264 assert.NoError(t, os.Setenv(coreutils.HomeDir, cliHome)) 265 } else { 266 assert.NoError(t, os.Unsetenv(coreutils.HomeDir)) 267 } 268 }() 269 tempDirPath, err := fileutils.CreateTempDir() 270 assert.NoError(t, err) 271 defer func() { 272 assert.NoError(t, fileutils.RemoveTempDir(tempDirPath), "Couldn't remove temp dir") 273 }() 274 assert.NoError(t, os.Setenv(coreutils.HomeDir, tempDirPath)) 275 assert.Error(t, Export("")) 276 } 277 278 func TestKeyEncryption(t *testing.T) { 279 cleanUpJfrogHome, err := utilsTests.SetJfrogHome() 280 assert.NoError(t, err) 281 defer cleanUpJfrogHome() 282 283 assert.NoError(t, os.Setenv(coreutils.EncryptionKey, "p3aNuTbUtt3rJ3lly&ChEEsEPlEasE!!")) 284 defer func() { 285 assert.NoError(t, os.Unsetenv(coreutils.EncryptionKey)) 286 }() 287 inputDetails := tests.CreateTestServerDetails() 288 inputDetails.User = "admin" 289 inputDetails.Password = "password" 290 291 configAndTest(t, inputDetails, true) 292 configAndTest(t, inputDetails, false) 293 } 294 295 func TestKeyDecryptionError(t *testing.T) { 296 cleanUpJfrogHome, err := utilsTests.SetJfrogHome() 297 assert.NoError(t, err) 298 defer cleanUpJfrogHome() 299 300 assert.NoError(t, os.Setenv(coreutils.EncryptionKey, "p3aNuTbUtt3rJ3lly&ChEEsEPlEasE!!")) 301 defer func() { 302 assert.NoError(t, os.Unsetenv(coreutils.EncryptionKey)) 303 }() 304 305 inputDetails := tests.CreateTestServerDetails() 306 inputDetails.User = "admin" 307 inputDetails.Password = "password" 308 309 // Configure server with JFROG_CLI_ENCRYPTION_KEY set 310 configCmd := NewConfigCommand(AddOrEdit, testServerId).SetDetails(inputDetails).SetUseBasicAuthOnly(true).SetInteractive(false) 311 configCmd.disablePrompts = true 312 assert.NoError(t, configCmd.Run()) 313 314 // Get the server details when JFROG_CLI_ENCRYPTION_KEY is not set and expect an error 315 assert.NoError(t, os.Unsetenv(coreutils.EncryptionKey)) 316 _, err = GetConfig(testServerId, false) 317 assert.ErrorContains(t, err, "cannot decrypt config") 318 } 319 320 func testExportImport(t *testing.T, inputDetails *config.ServerDetails) { 321 configToken, err := config.Export(inputDetails) 322 assert.NoError(t, err) 323 outputDetails, err := config.Import(configToken) 324 assert.NoError(t, err) 325 assert.Equal(t, configStructToString(t, inputDetails), configStructToString(t, outputDetails), "unexpected configuration was saved to file") 326 } 327 328 func configAndTest(t *testing.T, inputDetails *config.ServerDetails, interactive bool) { 329 outputConfig, err := configAndGetTestServer(t, inputDetails, true, interactive) 330 assert.NoError(t, err) 331 assert.Equal(t, configStructToString(t, inputDetails), configStructToString(t, outputConfig), "unexpected configuration was saved to file") 332 assert.NoError(t, NewConfigCommand(Delete, testServerId).Run()) 333 testExportImport(t, inputDetails) 334 } 335 336 func configAndGetTestServer(t *testing.T, inputDetails *config.ServerDetails, basicAuthOnly, interactive bool) (*config.ServerDetails, error) { 337 return configAndGetServer(t, testServerId, inputDetails, basicAuthOnly, interactive, false) 338 } 339 340 func configAndGetServer(t *testing.T, serverId string, inputDetails *config.ServerDetails, basicAuthOnly, interactive, makeDefault bool) (*config.ServerDetails, error) { 341 doConfig(t, serverId, inputDetails, basicAuthOnly, interactive, makeDefault) 342 return GetConfig(serverId, false) 343 } 344 345 func doConfig(t *testing.T, serverId string, inputDetails *config.ServerDetails, basicAuthOnly, interactive, makeDefault bool) { 346 configCmd := NewConfigCommand(AddOrEdit, serverId).SetDetails(inputDetails).SetUseBasicAuthOnly(basicAuthOnly). 347 SetInteractive(interactive).SetMakeDefault(makeDefault) 348 configCmd.disablePrompts = true 349 assert.NoError(t, configCmd.Run()) 350 } 351 352 func configStructToString(t *testing.T, artConfig *config.ServerDetails) string { 353 artConfig.IsDefault = false 354 marshaledStruct, err := json.Marshal(*artConfig) 355 assert.NoError(t, err) 356 return string(marshaledStruct) 357 }