Skip to content

Commit 7867238

Browse files
author
Dr. Q and Company
authored
Merge pull request #1 from drQedwards/copilot/fix-go-import-issue
fix: resolve CWE-665 — make xurl importable as a Go library
2 parents 4a56f8e + ad5449d commit 7867238

10 files changed

Lines changed: 363 additions & 24 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
xurl
1+
/xurl
22
.xurl_test
33
.DS_Store# Added by goreleaser init:
44
dist/

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
.PHONY: build
22
build:
3-
go build -o xurl
3+
go build -o xurl ./cmd/xurl
44

55
.PHONY: install
66
install:
7-
go install
7+
go install ./cmd/xurl
88

99
.PHONY: clean
1010
clean:

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,23 @@ Installs to `~/.local/bin`. If it's not in your PATH, the script will tell you w
3333

3434
### Go
3535
```bash
36-
go install github.com/xdevplatform/xurl@latest
36+
go install github.com/xdevplatform/xurl/cmd/xurl@latest
37+
```
38+
39+
### Use as a Go library
40+
41+
Import by module path in other Go projects:
42+
43+
```go
44+
import "github.com/xdevplatform/xurl"
45+
```
46+
47+
If your consuming project uses a local checkout of this repo, you can use a `replace` directive in your `go.mod` while still importing by the full module path:
48+
49+
```go
50+
require github.com/xdevplatform/xurl v0.0.0
51+
52+
replace github.com/xdevplatform/xurl => ../xurl
3753
```
3854

3955

api/client.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
package api
22

33
import (
4+
"bufio"
45
"bytes"
56
"encoding/json"
67
"errors"
78
"fmt"
89
"io"
10+
"mime/multipart"
911
"net/http"
12+
"os"
13+
"path/filepath"
1014
"strings"
1115
"time"
1216

13-
"bufio"
14-
"mime/multipart"
15-
"os"
16-
"path/filepath"
1717
"github.com/xdevplatform/xurl/auth"
1818
"github.com/xdevplatform/xurl/config"
1919
xurlErrors "github.com/xdevplatform/xurl/errors"
@@ -346,7 +346,8 @@ func (c *ApiClient) getAuthHeader(method, url string, authType string, username
346346
}
347347

348348
// If no auth type is specified, try to use the first OAuth2 token
349-
token := c.auth.TokenStore.GetFirstOAuth2Token()
349+
// Use ForApp variants so the active app name (set via --app) is respected.
350+
token := c.auth.TokenStore.GetFirstOAuth2TokenForApp(c.auth.AppName())
350351
if token != nil {
351352
accessToken, err := c.auth.GetOAuth2Header(username)
352353
if err == nil {
@@ -355,7 +356,7 @@ func (c *ApiClient) getAuthHeader(method, url string, authType string, username
355356
}
356357

357358
// If no OAuth2 token is available, try to use the first OAuth1 token
358-
token = c.auth.TokenStore.GetOAuth1Tokens()
359+
token = c.auth.TokenStore.GetOAuth1TokensForApp(c.auth.AppName())
359360
if token != nil {
360361
authHeader, err := c.auth.GetOAuth1Header(method, url, nil)
361362
if err == nil {

api/client_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"path/filepath"
99
"strings"
1010
"testing"
11+
"time"
1112

1213
"github.com/xdevplatform/xurl/auth"
1314
"github.com/xdevplatform/xurl/config"
@@ -356,3 +357,81 @@ func TestStreamRequest(t *testing.T) {
356357
assert.True(t, xurlErrors.IsAPIError(err), "Expected API error")
357358
})
358359
}
360+
361+
// futureExpiry returns a unix timestamp 1 hour in the future.
362+
func futureExpiry() uint64 {
363+
return uint64(time.Now().Add(time.Hour).Unix())
364+
}
365+
366+
// TC 5.3: ApiClient with multi-app Auth; app-b only has Bearer → BuildRequest uses app-b's Bearer
367+
func TestTC5_3_ApiClientUsesAppBBearerNotDefaultOAuth2(t *testing.T) {
368+
tempDir, err := os.MkdirTemp("", "xurl_api_multiapp_test")
369+
require.NoError(t, err)
370+
defer os.RemoveAll(tempDir)
371+
372+
tempFile := filepath.Join(tempDir, ".xurl")
373+
ts := &store.TokenStore{
374+
Apps: make(map[string]*store.App),
375+
DefaultApp: "app-a",
376+
FilePath: tempFile,
377+
}
378+
379+
// app-a: has OAuth2 (default/active for auto-selection cascade)
380+
ts.Apps["app-a"] = &store.App{
381+
ClientID: "id-a",
382+
ClientSecret: "secret-a",
383+
DefaultUser: "alice-a",
384+
OAuth2Tokens: map[string]store.Token{
385+
"alice-a": {
386+
Type: store.OAuth2TokenType,
387+
OAuth2: &store.OAuth2Token{
388+
AccessToken: "oauth2-token-alice-a",
389+
RefreshToken: "refresh-alice-a",
390+
ExpirationTime: futureExpiry(),
391+
},
392+
},
393+
},
394+
BearerToken: &store.Token{
395+
Type: store.BearerTokenType,
396+
Bearer: "bearer-a",
397+
},
398+
}
399+
400+
// app-b: has ONLY Bearer token, no OAuth2
401+
ts.Apps["app-b"] = &store.App{
402+
ClientID: "id-b",
403+
ClientSecret: "secret-b",
404+
OAuth2Tokens: make(map[string]store.Token),
405+
BearerToken: &store.Token{
406+
Type: store.BearerTokenType,
407+
Bearer: "bearer-b-only",
408+
},
409+
}
410+
411+
// Build Auth starting with app-a credentials
412+
a := auth.NewAuth(&config.Config{
413+
ClientID: "id-a",
414+
ClientSecret: "secret-a",
415+
APIBaseURL: "https://api.x.com",
416+
AuthURL: "https://x.com/i/oauth2/authorize",
417+
TokenURL: "https://api.x.com/2/oauth2/token",
418+
RedirectURI: "http://localhost:8080/callback",
419+
InfoURL: "https://api.x.com/2/users/me",
420+
}).WithTokenStore(ts)
421+
422+
// Switch to app-b
423+
a.WithAppName("app-b")
424+
425+
cfg := &config.Config{APIBaseURL: "https://api.x.com"}
426+
client := NewApiClient(cfg, a)
427+
428+
req, err := client.BuildRequest(RequestOptions{
429+
Method: "GET",
430+
Endpoint: "/2/users/me",
431+
})
432+
require.NoError(t, err)
433+
434+
authHeader := req.Header.Get("Authorization")
435+
assert.Equal(t, "Bearer bearer-b-only", authHeader,
436+
"Authorization header must use app-b's Bearer token, not app-a's OAuth2")
437+
}

api/media.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strconv"
1010
"strings"
1111
"time"
12+
1213
"github.com/xdevplatform/xurl/utils"
1314
)
1415

auth/auth.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,20 @@ func (a *Auth) WithAppName(appName string) *Auth {
8282
a.appName = appName
8383
app := a.TokenStore.ResolveApp(appName)
8484
if app != nil {
85-
if a.clientID == "" {
86-
a.clientID = app.ClientID
87-
}
88-
if a.clientSecret == "" {
89-
a.clientSecret = app.ClientSecret
90-
}
85+
a.clientID = app.ClientID
86+
a.clientSecret = app.ClientSecret
9187
}
9288
return a
9389
}
9490

91+
// AppName returns the current app name override (may be empty).
92+
func (a *Auth) AppName() string {
93+
return a.appName
94+
}
95+
9596
// GetOAuth1Header gets the OAuth1 header for a request
9697
func (a *Auth) GetOAuth1Header(method, urlStr string, additionalParams map[string]string) (string, error) {
97-
token := a.TokenStore.GetOAuth1Tokens()
98+
token := a.TokenStore.GetOAuth1TokensForApp(a.appName)
9899
if token == nil || token.OAuth1 == nil {
99100
return "", xurlErrors.NewAuthError("TokenNotFound", errors.New("OAuth1 token not found"))
100101
}
@@ -146,9 +147,9 @@ func (a *Auth) GetOAuth2Header(username string) (string, error) {
146147
var token *store.Token
147148

148149
if username != "" {
149-
token = a.TokenStore.GetOAuth2Token(username)
150+
token = a.TokenStore.GetOAuth2TokenForApp(a.appName, username)
150151
} else {
151-
token = a.TokenStore.GetFirstOAuth2Token()
152+
token = a.TokenStore.GetFirstOAuth2TokenForApp(a.appName)
152153
}
153154

154155
if token == nil {
@@ -253,7 +254,7 @@ func (a *Auth) OAuth2Flow(username string) (string, error) {
253254

254255
expirationTime := uint64(time.Now().Add(time.Duration(token.Expiry.Unix()-time.Now().Unix()) * time.Second).Unix())
255256

256-
err = a.TokenStore.SaveOAuth2Token(usernameStr, token.AccessToken, token.RefreshToken, expirationTime)
257+
err = a.TokenStore.SaveOAuth2TokenForApp(a.appName, usernameStr, token.AccessToken, token.RefreshToken, expirationTime)
257258
if err != nil {
258259
return "", xurlErrors.NewAuthError("TokenStorageError", err)
259260
}
@@ -266,9 +267,9 @@ func (a *Auth) RefreshOAuth2Token(username string) (string, error) {
266267
var token *store.Token
267268

268269
if username != "" {
269-
token = a.TokenStore.GetOAuth2Token(username)
270+
token = a.TokenStore.GetOAuth2TokenForApp(a.appName, username)
270271
} else {
271-
token = a.TokenStore.GetFirstOAuth2Token()
272+
token = a.TokenStore.GetFirstOAuth2TokenForApp(a.appName)
272273
}
273274

274275
if token == nil || token.OAuth2 == nil {
@@ -310,7 +311,7 @@ func (a *Auth) RefreshOAuth2Token(username string) (string, error) {
310311

311312
expirationTime := uint64(time.Now().Add(time.Duration(newToken.Expiry.Unix()-time.Now().Unix()) * time.Second).Unix())
312313

313-
err = a.TokenStore.SaveOAuth2Token(usernameStr, newToken.AccessToken, newToken.RefreshToken, expirationTime)
314+
err = a.TokenStore.SaveOAuth2TokenForApp(a.appName, usernameStr, newToken.AccessToken, newToken.RefreshToken, expirationTime)
314315
if err != nil {
315316
return "", xurlErrors.NewAuthError("RefreshTokenError", err)
316317
}
@@ -320,7 +321,7 @@ func (a *Auth) RefreshOAuth2Token(username string) (string, error) {
320321

321322
// GetBearerTokenHeader gets the bearer token from the token store
322323
func (a *Auth) GetBearerTokenHeader() (string, error) {
323-
token := a.TokenStore.GetBearerToken()
324+
token := a.TokenStore.GetBearerTokenForApp(a.appName)
324325
if token == nil {
325326
return "", xurlErrors.NewAuthError("TokenNotFound", errors.New("bearer token not found"))
326327
}

0 commit comments

Comments
 (0)