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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2311cffaf323ff4da73072897c6b07b9d47645d0768a5a897995e7380ae8dd51
4
- data.tar.gz: 9a51f18818d70dbf88c0b6b5501b88b17bb14f86ff34e0110f25c948823830d6
3
+ metadata.gz: 5c239226a8271dec0cad9730080cf7bb2a7c6993991280fac27a404383018548
4
+ data.tar.gz: 89bdbf18bd443df9102cefd1395b90065a76661054bbc6ed3f62f93f9254bf84
5
5
  SHA512:
6
- metadata.gz: f821f3309a480081de9b71f9fadc8d82793271deae5f4de6b1e6f2de114a9137c69f33f5daa2763772120a10f8ea1e07f64657599785f98b20d3767671a42e59
7
- data.tar.gz: ad1a5e7a656d0d2bdf0f79497a2ca53873e8679027fc659d2e59fb22364d2c74eecde36a7f5cea7dddc1da09a87461c24846380ce882ea50054dcf9e5db2e0e0
6
+ metadata.gz: 770b462b28818f311a71ab97576c8841624247a1e67bef23716457bdee0d2e5c45746123d3feef19c86f65922dacd0ff2ec0a21ec59616c510cc6b49467ae586
7
+ data.tar.gz: 55c4e1e8b5baffffa2952b863aa1ee18fa952ef5b58cd3bc804c876c0103a90b764a9e3e3eb4101d6ad9e71fe063e758d85fbe0ae99d7a6552ab5585b673502c
@@ -18,5 +18,5 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'haveapi-client', '~> 0.28.0'
21
+ spec.add_dependency 'haveapi-client', '~> 0.28.1'
22
22
  end
@@ -1,5 +1,5 @@
1
1
  module HaveAPI
2
2
  module GoClient
3
- VERSION = '0.28.0'.freeze
3
+ VERSION = '0.28.1'.freeze
4
4
  end
5
5
  end
@@ -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.descriptionURL(<%= go_string_literal(auth.revoke_url) %>)
37
+ revokeURL, err := client.oauth2DescriptionURL(<%= go_string_literal(auth.revoke_url) %>)
38
38
  if err != nil {
39
39
  return err
40
40
  }
@@ -13,7 +13,8 @@ type Client struct {
13
13
  // Options for authentication method
14
14
  Authentication Authenticator
15
15
 
16
- httpClient *http.Client
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) %>
@@ -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.0
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.0
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.0
25
+ version: 0.28.1
26
26
  description: Go client generator
27
27
  email:
28
28
  - jakub.skokan@vpsfree.cz