haveapi-go-client 0.28.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/haveapi-go-client.gemspec +1 -1
- data/lib/haveapi/go_client/version.rb +1 -1
- data/spec/integration/generator_spec.rb +109 -0
- data/template/authentication/oauth2.go.erb +1 -1
- data/template/client.go.erb +2 -1
- data/template/request.go.erb +65 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c239226a8271dec0cad9730080cf7bb2a7c6993991280fac27a404383018548
|
|
4
|
+
data.tar.gz: 89bdbf18bd443df9102cefd1395b90065a76661054bbc6ed3f62f93f9254bf84
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 770b462b28818f311a71ab97576c8841624247a1e67bef23716457bdee0d2e5c45746123d3feef19c86f65922dacd0ff2ec0a21ec59616c510cc6b49467ae586
|
|
7
|
+
data.tar.gz: 55c4e1e8b5baffffa2952b863aa1ee18fa952ef5b58cd3bc804c876c0103a90b764a9e3e3eb4101d6ad9e71fe063e758d85fbe0ae99d7a6552ab5585b673502c
|
data/haveapi-go-client.gemspec
CHANGED
|
@@ -746,6 +746,115 @@ RSpec.describe HaveAPI::GoClient::Generator do
|
|
|
746
746
|
end
|
|
747
747
|
end
|
|
748
748
|
|
|
749
|
+
it 'allows explicitly trusted OAuth2 revoke origins' do
|
|
750
|
+
Dir.mktmpdir('haveapi-go-client-oauth2-revoke-trusted-origin-') do |dir|
|
|
751
|
+
communicator = instance_double(
|
|
752
|
+
HaveAPI::Client::Communicator,
|
|
753
|
+
describe_api: oauth2_revoke_description(
|
|
754
|
+
revoke_url: 'http://auth.example/revoke'
|
|
755
|
+
)
|
|
756
|
+
)
|
|
757
|
+
allow(HaveAPI::Client::Communicator).to receive(:new).and_return(communicator)
|
|
758
|
+
|
|
759
|
+
generator = described_class.new(
|
|
760
|
+
'http://api.example',
|
|
761
|
+
dir,
|
|
762
|
+
module: 'example.com/haveapi-oauth2-revoke-trusted-origin',
|
|
763
|
+
package: 'client'
|
|
764
|
+
)
|
|
765
|
+
generator.generate
|
|
766
|
+
generator.go_fmt
|
|
767
|
+
|
|
768
|
+
File.write(File.join(dir, 'client', 'oauth2_revoke_trusted_origin_test.go'), <<~GO)
|
|
769
|
+
package client
|
|
770
|
+
|
|
771
|
+
import (
|
|
772
|
+
"io"
|
|
773
|
+
"net/http"
|
|
774
|
+
"net/url"
|
|
775
|
+
"strings"
|
|
776
|
+
"testing"
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
type trustedOriginRevokeTransport struct {
|
|
780
|
+
req *http.Request
|
|
781
|
+
body string
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
func (transport *trustedOriginRevokeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
785
|
+
if req.Body != nil {
|
|
786
|
+
body, err := io.ReadAll(req.Body)
|
|
787
|
+
if err != nil {
|
|
788
|
+
return nil, err
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if err := req.Body.Close(); err != nil {
|
|
792
|
+
return nil, err
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
transport.body = string(body)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
transport.req = req
|
|
799
|
+
|
|
800
|
+
return &http.Response{
|
|
801
|
+
StatusCode: 200,
|
|
802
|
+
Status: "200 OK",
|
|
803
|
+
Body: io.NopCloser(strings.NewReader("ok")),
|
|
804
|
+
Header: make(http.Header),
|
|
805
|
+
Request: req,
|
|
806
|
+
}, nil
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
func TestOAuth2RevokeTrustedOrigin(t *testing.T) {
|
|
810
|
+
transport := &trustedOriginRevokeTransport{}
|
|
811
|
+
token := "trusted-origin-token"
|
|
812
|
+
|
|
813
|
+
c := New("http://api.example")
|
|
814
|
+
if err := c.AllowOAuth2Origin("http://auth.example"); err != nil {
|
|
815
|
+
t.Fatalf("allow OAuth2 origin failed: %v", err)
|
|
816
|
+
}
|
|
817
|
+
c.SetHTTPClient(&http.Client{Transport: transport})
|
|
818
|
+
c.SetExistingOAuth2Auth(token)
|
|
819
|
+
|
|
820
|
+
if err := c.RevokeAccessToken(); err != nil {
|
|
821
|
+
t.Fatalf("revoke failed: %v", err)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if transport.req == nil {
|
|
825
|
+
t.Fatalf("expected revoke request")
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if got := transport.req.URL.String(); got != "http://auth.example/revoke" {
|
|
829
|
+
t.Fatalf("unexpected revoke URL: %s", got)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if got := transport.req.Header.Get("X-HaveAPI-OAuth2-Token"); got != token {
|
|
833
|
+
t.Fatalf("expected OAuth2 header %q, got %q", token, got)
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
form, err := url.ParseQuery(transport.body)
|
|
837
|
+
if err != nil {
|
|
838
|
+
t.Fatalf("invalid form body %q: %v", transport.body, err)
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if got := form.Get("token"); got != token {
|
|
842
|
+
t.Fatalf("expected revoke token %q, got %q in body %q", token, got, transport.body)
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
GO
|
|
846
|
+
|
|
847
|
+
go_out, go_err, go_status = Open3.capture3(
|
|
848
|
+
{ 'CGO_ENABLED' => '0' },
|
|
849
|
+
'go',
|
|
850
|
+
'test',
|
|
851
|
+
'./...',
|
|
852
|
+
chdir: dir
|
|
853
|
+
)
|
|
854
|
+
expect(go_status).to be_success, "go test failed: #{go_out}\n#{go_err}"
|
|
855
|
+
end
|
|
856
|
+
end
|
|
857
|
+
|
|
749
858
|
it 'escapes untrusted API descriptions when generating Go source' do
|
|
750
859
|
injected_path = <<~PATH.chomp
|
|
751
860
|
/safe",
|
|
@@ -34,7 +34,7 @@ func (client *Client) RevokeAccessToken() error {
|
|
|
34
34
|
form := url.Values{}
|
|
35
35
|
form.Set("token", auth.AccessToken)
|
|
36
36
|
|
|
37
|
-
revokeURL, err := client.
|
|
37
|
+
revokeURL, err := client.oauth2DescriptionURL(<%= go_string_literal(auth.revoke_url) %>)
|
|
38
38
|
if err != nil {
|
|
39
39
|
return err
|
|
40
40
|
}
|
data/template/client.go.erb
CHANGED
|
@@ -13,7 +13,8 @@ type Client struct {
|
|
|
13
13
|
// Options for authentication method
|
|
14
14
|
Authentication Authenticator
|
|
15
15
|
|
|
16
|
-
httpClient
|
|
16
|
+
httpClient *http.Client
|
|
17
|
+
oauth2TrustedOrigins map[string]struct{}
|
|
17
18
|
|
|
18
19
|
<% api.resources.each do |r| -%>
|
|
19
20
|
// Resource <%= go_comment_text(r.full_dot_name) %>
|
data/template/request.go.erb
CHANGED
|
@@ -28,6 +28,29 @@ func (client *Client) actionURL(path string) (string, error) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
func (client *Client) descriptionURL(rawURL string) (string, error) {
|
|
31
|
+
return client.descriptionURLWithTrustedOrigins(rawURL, nil)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func (client *Client) oauth2DescriptionURL(rawURL string) (string, error) {
|
|
35
|
+
return client.descriptionURLWithTrustedOrigins(rawURL, client.oauth2TrustedOrigins)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// AllowOAuth2Origin permits OAuth2 endpoints from an additional trusted origin.
|
|
39
|
+
func (client *Client) AllowOAuth2Origin(rawOrigin string) error {
|
|
40
|
+
origin, err := parseTrustedOrigin(rawOrigin)
|
|
41
|
+
if err != nil {
|
|
42
|
+
return err
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if client.oauth2TrustedOrigins == nil {
|
|
46
|
+
client.oauth2TrustedOrigins = make(map[string]struct{})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
client.oauth2TrustedOrigins[origin] = struct{}{}
|
|
50
|
+
return nil
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func (client *Client) descriptionURLWithTrustedOrigins(rawURL string, trustedOrigins map[string]struct{}) (string, error) {
|
|
31
54
|
base, err := client.parsedBaseURL()
|
|
32
55
|
if err != nil {
|
|
33
56
|
return "", err
|
|
@@ -42,8 +65,12 @@ func (client *Client) descriptionURL(rawURL string) (string, error) {
|
|
|
42
65
|
return "", fmt.Errorf("unsafe API description URL %q", rawURL)
|
|
43
66
|
}
|
|
44
67
|
|
|
68
|
+
if ref.Host != "" && ref.Scheme == "" {
|
|
69
|
+
return "", fmt.Errorf("unsafe API description URL %q", rawURL)
|
|
70
|
+
}
|
|
71
|
+
|
|
45
72
|
resolved := base.ResolveReference(ref)
|
|
46
|
-
if !sameOrigin(base, resolved) {
|
|
73
|
+
if !sameOrigin(base, resolved) && !originAllowed(resolved, trustedOrigins) {
|
|
47
74
|
return "", fmt.Errorf("API description URL %q is outside client origin", rawURL)
|
|
48
75
|
}
|
|
49
76
|
|
|
@@ -87,6 +114,43 @@ func sameOrigin(a *url.URL, b *url.URL) bool {
|
|
|
87
114
|
originPort(a) == originPort(b)
|
|
88
115
|
}
|
|
89
116
|
|
|
117
|
+
func originAllowed(u *url.URL, trustedOrigins map[string]struct{}) bool {
|
|
118
|
+
if len(trustedOrigins) == 0 {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_, ok := trustedOrigins[originKey(u)]
|
|
123
|
+
return ok
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func parseTrustedOrigin(rawOrigin string) (string, error) {
|
|
127
|
+
if rawOrigin == "" || strings.ContainsAny(rawOrigin, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f \x7f\\") {
|
|
128
|
+
return "", fmt.Errorf("invalid trusted OAuth2 origin %q", rawOrigin)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
parsed, err := url.Parse(rawOrigin)
|
|
132
|
+
if err != nil {
|
|
133
|
+
return "", err
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if parsed.Scheme == "" || parsed.Host == "" || parsed.User != nil ||
|
|
137
|
+
(parsed.Path != "" && parsed.Path != "/") ||
|
|
138
|
+
parsed.RawQuery != "" || parsed.Fragment != "" {
|
|
139
|
+
return "", fmt.Errorf("invalid trusted OAuth2 origin %q", rawOrigin)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return originKey(parsed), nil
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func originKey(u *url.URL) string {
|
|
146
|
+
host := strings.ToLower(u.Hostname())
|
|
147
|
+
if strings.Contains(host, ":") {
|
|
148
|
+
host = "[" + host + "]"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return strings.ToLower(u.Scheme) + "://" + host + ":" + originPort(u)
|
|
152
|
+
}
|
|
153
|
+
|
|
90
154
|
func originPort(u *url.URL) string {
|
|
91
155
|
port := u.Port()
|
|
92
156
|
if port != "" {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haveapi-go-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.28.
|
|
4
|
+
version: 0.28.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jakub Skokan
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.28.
|
|
18
|
+
version: 0.28.1
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.28.
|
|
25
|
+
version: 0.28.1
|
|
26
26
|
description: Go client generator
|
|
27
27
|
email:
|
|
28
28
|
- jakub.skokan@vpsfree.cz
|