-
-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #194 from rusq/browser-post-extractor
Fix browser Authentication
- Loading branch information
Showing
6 changed files
with
558 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package browser | ||
|
||
import ( | ||
"errors" | ||
"mime/multipart" | ||
"net/http" | ||
"net/url" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/playwright-community/playwright-go" | ||
) | ||
|
||
//go:generate mockgen -package browser -destination playwright_test.go github.com/playwright-community/playwright-go Request | ||
|
||
// tokenRE is the regexp that matches a valid Slack Client token. | ||
var tokenRE = regexp.MustCompile(`xoxc-[0-9]+-[0-9]+-[0-9]+-[0-9a-z]{64}`) | ||
|
||
const maxMultipartMem = 65536 | ||
|
||
var ( | ||
ErrNoToken = errors.New("no token found") | ||
ErrInvalidTokenValue = errors.New("invalid token value") | ||
ErrInvalidContentType = errors.New("invalid content-type header") | ||
) | ||
|
||
// extractToken extracts token from the request. | ||
func extractToken(r playwright.Request) (string, error) { | ||
if r.Method() == http.MethodGet { | ||
return extractTokenGet(r.URL()) | ||
} else if r.Method() == http.MethodPost { | ||
return extractTokenPost(r) | ||
} | ||
return "", errors.New("invalid request method") | ||
} | ||
|
||
// extractTokenGet extracts token from the query string. | ||
func extractTokenGet(uri string) (string, error) { | ||
p, err := url.Parse(strings.TrimSpace(uri)) | ||
if err != nil { | ||
return "", err | ||
} | ||
q := p.Query() | ||
token := q.Get("token") | ||
if token == "" { | ||
return "", ErrNoToken | ||
} | ||
if !tokenRE.MatchString(token) { | ||
return "", ErrInvalidTokenValue | ||
} | ||
return token, nil | ||
} | ||
|
||
// extractTokenPost extracts token from the request body. | ||
func extractTokenPost(r playwright.Request) (string, error) { | ||
boundary, err := boundary(r) | ||
if err != nil { | ||
return "", err | ||
} | ||
data, err := r.PostData() | ||
if err != nil { | ||
return "", err | ||
} | ||
return tokenFromMultipart(data, boundary) | ||
} | ||
|
||
// tokenFromMultipart extracts token from the multipart form. | ||
func tokenFromMultipart(s string, boundary string) (string, error) { | ||
mp := multipart.NewReader(strings.NewReader(s), boundary) | ||
form, err := mp.ReadForm(maxMultipartMem) | ||
if err != nil { | ||
return "", err | ||
} | ||
tok, ok := form.Value["token"] | ||
if !ok { | ||
return "", errors.New("token not found") | ||
} | ||
if len(tok) != 1 { | ||
return "", errors.New("invalid token value") | ||
} | ||
if !tokenRE.MatchString(tok[0]) { | ||
return "", errors.New("invalid token value") | ||
} | ||
return tok[0], nil | ||
} | ||
|
||
// boundary extracts boundary from the request. | ||
func boundary(r playwright.Request) (string, error) { | ||
values, err := r.HeaderValues("Content-Type") | ||
if err != nil { | ||
return "", err | ||
} | ||
if len(values) != 1 { | ||
return "", ErrInvalidContentType | ||
} | ||
contentType, boundary, found := strings.Cut(values[0], "; boundary=") | ||
if !found || contentType != "multipart/form-data" { | ||
return "", ErrInvalidContentType | ||
} | ||
return boundary, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package browser | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
gomock "github.com/golang/mock/gomock" | ||
) | ||
|
||
const testMultipart = "-----------------------------37168696061856579082739228613\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nxoxc-888888888888-888888888888-8888888888888-fffffffffffffffa915fe069d70a8ad81743b0ec4ee9c81540af43f5e143264b\r\n-----------------------------37168696061856579082739228613\r\nContent-Disposition: form-data; name=\"platform\"\r\n\r\nsonic\r\n-----------------------------37168696061856579082739228613\r\nContent-Disposition: form-data; name=\"_x_should_cache\"\r\n\r\nfalse\r\n-----------------------------37168696061856579082739228613\r\nContent-Disposition: form-data; name=\"_x_allow_cached\"\r\n\r\ntrue\r\n-----------------------------37168696061856579082739228613\r\nContent-Disposition: form-data; name=\"_x_team_id\"\r\n\r\nTFCSDNRL5\r\n-----------------------------37168696061856579082739228613\r\nContent-Disposition: form-data; name=\"_x_gantry\"\r\n\r\ntrue\r\n-----------------------------37168696061856579082739228613\r\nContent-Disposition: form-data; name=\"_x_sonic\"\r\n\r\ntrue\r\n-----------------------------37168696061856579082739228613--\r\n" | ||
|
||
var testHdrValues = []string{ | ||
"multipart/form-data; boundary=---------------------------37168696061856579082739228613", | ||
} | ||
|
||
const testBoundary = "---------------------------37168696061856579082739228613" | ||
|
||
func Test_extractTokenGet(t *testing.T) { | ||
type args struct { | ||
uri string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
wantErr bool | ||
}{ | ||
{ | ||
"ok", | ||
args{"https://ora600.slack.com/api/api.features?_x_id=noversion-1651817410.129&token=xoxc-610187951300-604451271234-3473161557912-4c426dd426a45208707725b710302b32dda0ab002b80ccd8c4c8ac9971a11558&platform=sonic&_x_should_cache=false&_x_allow_cached=true&_x_team_id=THY5HTZ8U&_x_gantry=true&fp=7c\n"}, | ||
"xoxc-610187951300-604451271234-3473161557912-4c426dd426a45208707725b710302b32dda0ab002b80ccd8c4c8ac9971a11558", | ||
false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := extractTokenGet(tt.args.uri) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("extractToken() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if got != tt.want { | ||
t.Errorf("extractToken() got = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Test_tokenFromMultipart(t *testing.T) { | ||
type args struct { | ||
s string | ||
boundary string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
wantErr bool | ||
}{ | ||
{"ok", args{testMultipart, testBoundary}, "xoxc-888888888888-888888888888-8888888888888-fffffffffffffffa915fe069d70a8ad81743b0ec4ee9c81540af43f5e143264b", false}, | ||
{"bad boundary", args{testMultipart, "bad"}, "", true}, | ||
{"bad multipart", args{"bad", testBoundary}, "", true}, | ||
{"empty", args{"", testBoundary}, "", true}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := tokenFromMultipart(tt.args.s, tt.args.boundary) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("extractTokenPost() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if got != tt.want { | ||
t.Errorf("extractTokenPost() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Test_boundary(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
expect func(r *MockRequest) | ||
want string | ||
wantErr bool | ||
}{ | ||
{ | ||
"ok", | ||
func(r *MockRequest) { | ||
r.EXPECT().HeaderValues("Content-Type").Return(testHdrValues, nil) | ||
}, | ||
testBoundary, | ||
false, | ||
}, | ||
{ | ||
"no header", | ||
func(r *MockRequest) { | ||
r.EXPECT().HeaderValues("Content-Type").Return(nil, nil) | ||
}, | ||
"", | ||
true, | ||
}, | ||
{ | ||
"bad header", | ||
func(r *MockRequest) { | ||
r.EXPECT().HeaderValues("Content-Type").Return([]string{"bad"}, nil) | ||
}, | ||
"", | ||
true, | ||
}, | ||
{ | ||
"error", | ||
func(r *MockRequest) { | ||
r.EXPECT().HeaderValues("Content-Type").Return(nil, errors.New("bad")) | ||
}, | ||
"", | ||
true, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
ctrl := gomock.NewController(t) | ||
mr := NewMockRequest(ctrl) | ||
tt.expect(mr) | ||
got, err := boundary(mr) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("boundary() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if got != tt.want { | ||
t.Errorf("boundary() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.