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