github.com/pelicanplatform/pelican@v1.0.5/client/main_test.go (about)

     1  /***************************************************************
     2   *
     3   * Copyright (C) 2023, University of Nebraska-Lincoln
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License"); you
     6   * may not use this file except in compliance with the License.  You may
     7   * obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   ***************************************************************/
    18  
    19  package client
    20  
    21  import (
    22  	"net"
    23  	"net/url"
    24  	"os"
    25  	"path/filepath"
    26  	"strconv"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/spf13/viper"
    31  	"github.com/stretchr/testify/assert"
    32  
    33  	"github.com/pelicanplatform/pelican/config"
    34  	"github.com/pelicanplatform/pelican/namespaces"
    35  )
    36  
    37  // TestGetIps calls main.get_ips with a hostname, checking
    38  // for a valid return value.
    39  func TestGetIps(t *testing.T) {
    40  	t.Parallel()
    41  
    42  	ips := get_ips("wlcg-wpad.fnal.gov")
    43  	for _, ip := range ips {
    44  		parsedIP := net.ParseIP(ip)
    45  		if parsedIP.To4() != nil {
    46  			// Make sure that the ip doesn't start with a "[", breaks downloads
    47  			if strings.HasPrefix(ip, "[") {
    48  				t.Fatal("IPv4 address has brackets, will break downloads")
    49  			}
    50  		} else if parsedIP.To16() != nil {
    51  			if !strings.HasPrefix(ip, "[") {
    52  				t.Fatal("IPv6 address doesn't have brackets, downloads will parse it as invalid ports")
    53  			}
    54  		}
    55  	}
    56  
    57  }
    58  
    59  // TestGetToken tests getToken
    60  func TestGetToken(t *testing.T) {
    61  
    62  	// Need a namespace for token acquisition
    63  	defer os.Unsetenv("PELICAN_FEDERATION_TOPOLOGYNAMESPACEURL")
    64  	os.Setenv("PELICAN_TOPOLOGY_NAMESPACE_URL", "https://topology.opensciencegrid.org/osdf/namespaces")
    65  	viper.Reset()
    66  	err := config.InitClient()
    67  	assert.Nil(t, err)
    68  
    69  	namespace, err := namespaces.MatchNamespace("/user/foo")
    70  	assert.NoError(t, err)
    71  
    72  	url, err := url.Parse("osdf:///user/foo")
    73  	assert.NoError(t, err)
    74  
    75  	// ENVs to test: BEARER_TOKEN, BEARER_TOKEN_FILE, XDG_RUNTIME_DIR/bt_u<uid>, TOKEN, _CONDOR_CREDS/scitoken.use, .condor_creds/scitokens.use
    76  	os.Setenv("BEARER_TOKEN", "bearer_token_contents")
    77  	token, err := getToken(url, namespace, true, "")
    78  	assert.NoError(t, err)
    79  	assert.Equal(t, "bearer_token_contents", token)
    80  	os.Unsetenv("BEARER_TOKEN")
    81  
    82  	// BEARER_TOKEN_FILE
    83  	tmpDir := t.TempDir()
    84  	token_contents := "bearer_token_file_contents"
    85  	tmpFile := []byte(token_contents)
    86  	bearer_token_file := filepath.Join(tmpDir, "bearer_token_file")
    87  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
    88  	assert.NoError(t, err)
    89  	os.Setenv("BEARER_TOKEN_FILE", bearer_token_file)
    90  	token, err = getToken(url, namespace, true, "")
    91  	assert.NoError(t, err)
    92  	assert.Equal(t, token_contents, token)
    93  	os.Unsetenv("BEARER_TOKEN_FILE")
    94  
    95  	// XDG_RUNTIME_DIR/bt_u<uid>
    96  	token_contents = "bearer_token_file_contents xdg"
    97  	tmpFile = []byte(token_contents)
    98  	bearer_token_file = filepath.Join(tmpDir, "bt_u"+strconv.Itoa(os.Getuid()))
    99  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   100  	assert.NoError(t, err)
   101  	os.Setenv("XDG_RUNTIME_DIR", tmpDir)
   102  	token, err = getToken(url, namespace, true, "")
   103  	assert.NoError(t, err)
   104  	assert.Equal(t, token_contents, token)
   105  	os.Unsetenv("XDG_RUNTIME_DIR")
   106  
   107  	// TOKEN
   108  	token_contents = "bearer_token_file_contents token"
   109  	tmpFile = []byte(token_contents)
   110  	bearer_token_file = filepath.Join(tmpDir, "token_file")
   111  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   112  	assert.NoError(t, err)
   113  	os.Setenv("TOKEN", bearer_token_file)
   114  	token, err = getToken(url, namespace, true, "")
   115  	assert.NoError(t, err)
   116  	assert.Equal(t, token_contents, token)
   117  	os.Unsetenv("TOKEN")
   118  
   119  	// _CONDOR_CREDS/scitokens.use
   120  	token_contents = "bearer_token_file_contents scitokens.use"
   121  	tmpFile = []byte(token_contents)
   122  	bearer_token_file = filepath.Join(tmpDir, "scitokens.use")
   123  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   124  	assert.NoError(t, err)
   125  	os.Setenv("_CONDOR_CREDS", tmpDir)
   126  	token, err = getToken(url, namespace, true, "")
   127  	assert.NoError(t, err)
   128  	assert.Equal(t, token_contents, token)
   129  	os.Unsetenv("_CONDOR_CREDS")
   130  
   131  	// _CONDOR_CREDS/renamed.use
   132  	token_contents = "bearer_token_file_contents renamed.use"
   133  	tmpFile = []byte(token_contents)
   134  	tmpDir = t.TempDir()
   135  	bearer_token_file = filepath.Join(tmpDir, "renamed.use")
   136  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   137  	assert.NoError(t, err)
   138  	os.Setenv("_CONDOR_CREDS", tmpDir)
   139  	renamedUrl, err := url.Parse("renamed+osdf:///user/ligo/frames")
   140  	assert.NoError(t, err)
   141  	renamedNamespace, err := namespaces.MatchNamespace("/user/ligo/frames")
   142  	assert.NoError(t, err)
   143  	token, err = getToken(renamedUrl, renamedNamespace, false, "")
   144  	assert.NoError(t, err)
   145  	assert.Equal(t, token_contents, token)
   146  	os.Unsetenv("_CONDOR_CREDS")
   147  
   148  	// _CONDOR_CREDS/renamed_handle1.use via renamed_handle1+osdf:///user/ligo/frames
   149  	token_contents = "bearer_token_file_contents renamed_handle1.use"
   150  	tmpFile = []byte(token_contents)
   151  	tmpDir = t.TempDir()
   152  	bearer_token_file = filepath.Join(tmpDir, "renamed_handle1.use")
   153  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   154  	assert.NoError(t, err)
   155  	os.Setenv("_CONDOR_CREDS", tmpDir)
   156  	// Use a valid URL, then replace the scheme
   157  	renamedUrl, err = url.Parse("renamed.handle1+osdf:///user/ligo/frames")
   158  	renamedUrl.Scheme = "renamed_handle1+osdf"
   159  	assert.NoError(t, err)
   160  	renamedNamespace, err = namespaces.MatchNamespace("/user/ligo/frames")
   161  	assert.NoError(t, err)
   162  	token, err = getToken(renamedUrl, renamedNamespace, false, "")
   163  	assert.NoError(t, err)
   164  	assert.Equal(t, token_contents, token)
   165  	os.Unsetenv("_CONDOR_CREDS")
   166  
   167  	// _CONDOR_CREDS/renamed_handle2.use via renamed.handle2+osdf:///user/ligo/frames
   168  	token_contents = "bearer_token_file_contents renamed.handle2.use"
   169  	tmpFile = []byte(token_contents)
   170  	tmpDir = t.TempDir()
   171  	bearer_token_file = filepath.Join(tmpDir, "renamed_handle2.use")
   172  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   173  	assert.NoError(t, err)
   174  	os.Setenv("_CONDOR_CREDS", tmpDir)
   175  	renamedUrl, err = url.Parse("renamed.handle2+osdf:///user/ligo/frames")
   176  	assert.NoError(t, err)
   177  	renamedNamespace, err = namespaces.MatchNamespace("/user/ligo/frames")
   178  	assert.NoError(t, err)
   179  	token, err = getToken(renamedUrl, renamedNamespace, false, "")
   180  	assert.NoError(t, err)
   181  	assert.Equal(t, token_contents, token)
   182  	os.Unsetenv("_CONDOR_CREDS")
   183  
   184  	// _CONDOR_CREDS/renamed.handle3.use via renamed.handle3+osdf:///user/ligo/frames
   185  	token_contents = "bearer_token_file_contents renamed.handle3.use"
   186  	tmpFile = []byte(token_contents)
   187  	tmpDir = t.TempDir()
   188  	bearer_token_file = filepath.Join(tmpDir, "renamed.handle3.use")
   189  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   190  	assert.NoError(t, err)
   191  	os.Setenv("_CONDOR_CREDS", tmpDir)
   192  	renamedUrl, err = url.Parse("renamed.handle3+osdf:///user/ligo/frames")
   193  	assert.NoError(t, err)
   194  	renamedNamespace, err = namespaces.MatchNamespace("/user/ligo/frames")
   195  	assert.NoError(t, err)
   196  	token, err = getToken(renamedUrl, renamedNamespace, false, "")
   197  	assert.NoError(t, err)
   198  	assert.Equal(t, token_contents, token)
   199  	os.Unsetenv("_CONDOR_CREDS")
   200  
   201  	// _CONDOR_CREDS/renamed.use
   202  	token_contents = "bearer_token_file_contents renamed.use"
   203  	tmpFile = []byte(token_contents)
   204  	tmpDir = t.TempDir()
   205  	bearer_token_file = filepath.Join(tmpDir, "renamed.use")
   206  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   207  	assert.NoError(t, err)
   208  	os.Setenv("_CONDOR_CREDS", tmpDir)
   209  	renamedUrl, err = url.Parse("/user/ligo/frames")
   210  	assert.NoError(t, err)
   211  	renamedNamespace, err = namespaces.MatchNamespace("/user/ligo/frames")
   212  	assert.NoError(t, err)
   213  	token, err = getToken(renamedUrl, renamedNamespace, false, "renamed")
   214  	assert.NoError(t, err)
   215  	assert.Equal(t, token_contents, token)
   216  	os.Unsetenv("_CONDOR_CREDS")
   217  
   218  	// Current directory .condor_creds/scitokens.use
   219  	token_contents = "bearer_token_file_contents .condor_creds/scitokens.use"
   220  	tmpFile = []byte(token_contents)
   221  	bearer_token_file = filepath.Join(tmpDir, ".condor_creds", "scitokens.use")
   222  	err = os.Mkdir(filepath.Join(tmpDir, ".condor_creds"), 0755)
   223  	assert.NoError(t, err)
   224  	err = os.WriteFile(bearer_token_file, tmpFile, 0644)
   225  	assert.NoError(t, err)
   226  	currentDir, err := os.Getwd()
   227  	assert.NoError(t, err)
   228  	err = os.Chdir(tmpDir)
   229  	assert.NoError(t, err)
   230  	token, err = getToken(url, namespace, true, "")
   231  	assert.NoError(t, err)
   232  	assert.Equal(t, token_contents, token)
   233  	err = os.Chdir(currentDir)
   234  	assert.NoError(t, err)
   235  
   236  	ObjectClientOptions.Plugin = true
   237  	_, err = getToken(url, namespace, true, "")
   238  	assert.EqualError(t, err, "Credential is required for osdf:///user/foo but is currently missing")
   239  	ObjectClientOptions.Plugin = false
   240  
   241  }
   242  
   243  // TestGetTokenName tests getTokenName
   244  func TestGetTokenName(t *testing.T) {
   245  	cases := []struct {
   246  		url    string
   247  		name   string
   248  		scheme string
   249  	}{
   250  		{"osdf://blah+asdf", "", "osdf"},
   251  		{"stash://blah+asdf", "", "stash"},
   252  		{"file://blah+asdf", "", "file"},
   253  		{"tokename+osdf://blah+asdf", "tokename", "osdf"},
   254  		{"tokename+stash://blah+asdf", "tokename", "stash"},
   255  		{"tokename+file://blah+asdf", "tokename", "file"},
   256  		{"tokename+tokename2+osdf://blah+asdf", "tokename+tokename2", "osdf"},
   257  		{"token+tokename2+stash://blah+asdf", "token+tokename2", "stash"},
   258  		{"token.use+stash://blah+asdf", "token.use", "stash"},
   259  		{"token.blah.asdf+stash://blah+asdf", "token.blah.asdf", "stash"},
   260  	}
   261  	for _, c := range cases {
   262  		url, err := url.Parse(c.url)
   263  		assert.NoError(t, err)
   264  		scheme, tokenName := getTokenName(url)
   265  		assert.Equal(t, c.name, tokenName)
   266  		assert.Equal(t, c.scheme, scheme)
   267  	}
   268  
   269  }
   270  
   271  func FuzzGetTokenName(f *testing.F) {
   272  	testcases := []string{"", "tokename", "tokename+tokename2"}
   273  	for _, tc := range testcases {
   274  		f.Add(tc) // Use f.Add to provide a seed corpus
   275  	}
   276  	f.Fuzz(func(t *testing.T, orig string) {
   277  		// Make sure it's a valid URL
   278  		urlString := orig + "+osdf://blah+asdf"
   279  		url, err := url.Parse(urlString)
   280  		// If it's not a valid URL, then it's not a valid token name
   281  		if err != nil || url.Scheme == "" {
   282  			return
   283  		}
   284  		assert.NoError(t, err)
   285  		_, tokenName := getTokenName(url)
   286  		assert.Equal(t, strings.ToLower(orig), tokenName, "URL: "+urlString+"URL String: "+url.String()+" Scheme: "+url.Scheme)
   287  	})
   288  }
   289  
   290  func TestCorrectURLWithUnderscore(t *testing.T) {
   291  	tests := []struct {
   292  		name           string
   293  		url            string
   294  		expectedURL    string
   295  		expectedScheme string
   296  	}{
   297  		{
   298  			name:           "LIGO URL with underscore",
   299  			url:            "ligo_data://ligo.org/data/1",
   300  			expectedURL:    "ligo.data://ligo.org/data/1",
   301  			expectedScheme: "ligo_data",
   302  		},
   303  		{
   304  			name:           "URL without underscore",
   305  			url:            "http://example.com",
   306  			expectedURL:    "http://example.com",
   307  			expectedScheme: "http",
   308  		},
   309  		{
   310  			name:           "URL with no scheme",
   311  			url:            "example.com",
   312  			expectedURL:    "example.com",
   313  			expectedScheme: "",
   314  		},
   315  	}
   316  
   317  	for _, tt := range tests {
   318  		t.Run(tt.name, func(t *testing.T) {
   319  			actualURL, actualScheme := correctURLWithUnderscore(tt.url)
   320  			if actualURL != tt.expectedURL || actualScheme != tt.expectedScheme {
   321  				t.Errorf("correctURLWithUnderscore(%v) = %v, %v; want %v, %v", tt.url, actualURL, actualScheme, tt.expectedURL, tt.expectedScheme)
   322  			}
   323  		})
   324  	}
   325  }
   326  
   327  func TestParseNoJobAd(t *testing.T) {
   328  	// Job ad file does not exist
   329  	tempDir := t.TempDir()
   330  	path := filepath.Join(tempDir, ".job.ad")
   331  	os.Setenv("_CONDOR_JOB_AD", path)
   332  
   333  	payload := payloadStruct{}
   334  	parse_job_ad(payload)
   335  }