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  }