Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 77 additions & 29 deletions client/acquire_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,43 +501,91 @@ func tokenIsAcceptable(jwtSerialized string, objectName string, dirResp server_s
}

scopes_iface, ok := tok.Get("scope")

// Build the set of scope types required by the requested operations.
// Each entry maps a required scope-type predicate to a "satisfied" flag.
type requirement struct {
matches func(string) bool
satisfied bool
}
var requirements []*requirement

if opts.Operation.IsEnabled(config.TokenRead) || opts.Operation.IsEnabled(config.TokenSharedRead) || opts.Operation.IsEnabled(config.TokenList) {
requirements = append(requirements, &requirement{
matches: func(s string) bool {
if s == "storage.read" {
return true
}
return isSci && s == "read"
},
})
}
if opts.Operation.IsEnabled(config.TokenWrite) || opts.Operation.IsEnabled(config.TokenSharedWrite) {
requirements = append(requirements, &requirement{
matches: func(s string) bool {
if s == "storage.modify" || s == "storage.create" {
return true
}
return isSci && s == "write"
},
})
}
if opts.Operation.IsEnabled(config.TokenDelete) {
requirements = append(requirements, &requirement{
matches: func(s string) bool {
if s == "storage.modify" {
return true
}
return isSci && s == "write"
},
})
}

// If operations are requested but the token has no scopes at all, reject.
if len(requirements) == 0 {
return false
}
if !ok {
return false
}
if scopes, ok := scopes_iface.(string); ok {
acceptableScope := false
for _, scope := range strings.Split(scopes, " ") {
scope_info := strings.Split(scope, ":")
var scopeOK bool
if opts.Operation.IsEnabled(config.TokenWrite) || opts.Operation.IsEnabled(config.TokenSharedWrite) {
scopeOK = (scope_info[0] == "storage.modify" || scope_info[0] == "storage.create")
} else if opts.Operation.IsEnabled(config.TokenDelete) {
scopeOK = (scope_info[0] == "storage.modify")
} else if opts.Operation.IsEnabled(config.TokenRead) || opts.Operation.IsEnabled(config.TokenSharedRead) {
scopeOK = (scope_info[0] == "storage.read")
} else {
scopeOK = false
}
if !scopeOK {
continue
}
scopes, ok := scopes_iface.(string)
if !ok || scopes == "" {
return false
}

if len(scope_info) == 1 {
acceptableScope = true
break
}
// Shared URLs must have exact matches; otherwise, prefix matching is acceptable.
if ((opts.Operation.IsEnabled(config.TokenSharedWrite) || opts.Operation.IsEnabled(config.TokenSharedRead)) && (targetResource == scope_info[1])) ||
strings.HasPrefix(targetResource, scope_info[1]) {
acceptableScope = true
break
// Check each token scope against every unsatisfied requirement.
for _, scope := range strings.Split(scopes, " ") {
scope_info := strings.Split(scope, ":")

// Verify the scope path is acceptable.
pathOK := false
if len(scope_info) == 1 {
pathOK = true
} else if opts.Operation.IsEnabled(config.TokenSharedWrite) || opts.Operation.IsEnabled(config.TokenSharedRead) {
// Shared operations require an exact path match.
pathOK = targetResource == scope_info[1]
} else {
pathOK = strings.HasPrefix(targetResource, scope_info[1])
}
if !pathOK {
continue
}

// Mark any requirement whose scope-type predicate matches.
for _, req := range requirements {
if !req.satisfied && req.matches(scope_info[0]) {
req.satisfied = true
}
}
if acceptableScope {
return true
}

// All requirements must be satisfied.
for _, req := range requirements {
if !req.satisfied {
return false
}
}
return false
return true
}

// Return whether the JWT represented by jwtSerialized is valid.
Expand Down
4 changes: 2 additions & 2 deletions client/bearer_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestBearerAuthenticator_Authorize(t *testing.T) {
}))
defer server.Close()

token := NewTokenGenerator(nil, nil, config.TokenSharedRead, false)
token := NewTokenGenerator(nil, nil, config.TokenRead, false)
token.SetToken("some_token_1234_abc")
authenticator := &bearerAuthenticator{token: token}
client := &http.Client{}
Expand All @@ -58,7 +58,7 @@ func TestBearerAuthenticator_Authorize(t *testing.T) {

// Test the retry logic for bearer authentication
func TestBearerAuthenticator_Verify(t *testing.T) {
token := NewTokenGenerator(nil, nil, config.TokenSharedRead, false)
token := NewTokenGenerator(nil, nil, config.TokenRead, false)
token.SetToken("some_token_1234_abc")
authenticator := &bearerAuthenticator{token: token}

Expand Down
8 changes: 4 additions & 4 deletions client/handle_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -1156,9 +1156,9 @@ func (tc *TransferClient) NewTransferJob(ctx context.Context, remoteUrl *url.URL
if _, exists := copyUrl.Query()[pelican_url.QueryRecursive]; exists {
recursive = true
}
operation := config.TokenSharedRead
operation := config.TokenRead
if upload {
operation = config.TokenSharedWrite
operation = config.TokenWrite
}
tj = &TransferJob{
prefObjServers: tc.prefObjServers,
Expand Down Expand Up @@ -1303,7 +1303,7 @@ func (tc *TransferClient) NewPrestageJob(ctx context.Context, remoteUrl *url.URL
xferType: transferTypePrestage,
uuid: id,
project: project,
token: NewTokenGenerator(&copyUrl, nil, config.TokenSharedRead, !tc.skipAcquire),
token: NewTokenGenerator(&copyUrl, nil, config.TokenRead, !tc.skipAcquire),
}
if tc.token != "" {
tj.token.SetToken(tc.token)
Expand Down Expand Up @@ -1403,7 +1403,7 @@ func (tc *TransferClient) CacheInfo(ctx context.Context, remoteUrl *url.URL, opt
}

var prefObjServers []*url.URL
token := NewTokenGenerator(pelicanURL, nil, config.TokenSharedRead, true)
token := NewTokenGenerator(pelicanURL, nil, config.TokenRead, true)
if tc.token != "" {
token.SetToken(tc.token)
}
Expand Down
14 changes: 7 additions & 7 deletions client/handle_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ func TestSortAttempts(t *testing.T) {

defer cancel()

token := NewTokenGenerator(nil, nil, config.TokenSharedRead, false)
token := NewTokenGenerator(nil, nil, config.TokenRead, false)
token.SetToken("aaa")
size, results := sortAttempts(ctx, "/path", []transferAttemptDetails{attempt1, attempt2, attempt3}, token)
assert.Equal(t, int64(42), size)
Expand Down Expand Up @@ -2324,7 +2324,7 @@ func TestHeadRequestWithDownloadToken(t *testing.T) {
svrURL, err := url.Parse(svr.URL)
require.NoError(t, err)

token := NewTokenGenerator(nil, nil, config.TokenSharedRead, false)
token := NewTokenGenerator(nil, nil, config.TokenRead, false)
token.SetToken("test-token")
transfer := &transferFile{
xferType: transferTypeDownload,
Expand Down Expand Up @@ -2807,7 +2807,7 @@ func TestPutOverwrite(t *testing.T) {
require.NoError(t, err)

// Create a token generator with a test token
token := NewTokenGenerator(nil, nil, config.TokenSharedWrite, false)
token := NewTokenGenerator(nil, nil, config.TokenWrite, false)
token.SetToken("test-token")

// Create a test file to upload
Expand Down Expand Up @@ -2867,7 +2867,7 @@ func TestPutOverwrite(t *testing.T) {
require.NoError(t, err)

// Create a token generator with a test token
token := NewTokenGenerator(nil, nil, config.TokenSharedWrite, false)
token := NewTokenGenerator(nil, nil, config.TokenWrite, false)
token.SetToken("test-token")

// Create a test file to upload
Expand Down Expand Up @@ -2928,7 +2928,7 @@ func TestPutOverwrite(t *testing.T) {
require.NoError(t, err)

// Create a token generator with a test token
token := NewTokenGenerator(nil, nil, config.TokenSharedWrite, false)
token := NewTokenGenerator(nil, nil, config.TokenWrite, false)
token.SetToken("test-token")

// Create a test file to upload
Expand Down Expand Up @@ -3032,7 +3032,7 @@ func TestPutOverwrite(t *testing.T) {
require.NoError(t, err)

// Create a token generator with a test token
token := NewTokenGenerator(nil, nil, config.TokenSharedWrite, false)
token := NewTokenGenerator(nil, nil, config.TokenWrite, false)
token.SetToken("test-token")

// Create a test file to upload
Expand Down Expand Up @@ -3166,7 +3166,7 @@ func TestPermissionDeniedError(t *testing.T) {
}
tj := &TransferJob{
remoteURL: remoteURL,
token: NewTokenGenerator(remoteURL, nil, config.TokenSharedRead, false),
token: NewTokenGenerator(remoteURL, nil, config.TokenRead, false),
}
transfer := &transferFile{
ctx: context.Background(),
Expand Down
4 changes: 2 additions & 2 deletions client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func DoStat(ctx context.Context, destination string, options ...TransferOption)

var requestedChecksums []ChecksumType

token := NewTokenGenerator(pUrl, &dirResp, config.TokenSharedRead, true)
token := NewTokenGenerator(pUrl, &dirResp, config.TokenRead, true)
for _, option := range options {
switch option.Ident() {
case identTransferOptionTokenLocation{}:
Expand Down Expand Up @@ -427,7 +427,7 @@ func DoList(ctx context.Context, remoteObject string, options ...TransferOption)
}

// Get our token if needed
token := NewTokenGenerator(pUrl, &dirResp, config.TokenSharedRead, true)
token := NewTokenGenerator(pUrl, &dirResp, config.TokenRead, true)
collectionsOverride := ""
recursive := false
depth := -1
Expand Down
Loading
Loading