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 }