auth0 5.8.1 → 5.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.bundle/config +2 -1
  3. data/.circleci/config.yml +6 -4
  4. data/.devcontainer/Dockerfile +19 -0
  5. data/.devcontainer/devcontainer.json +37 -0
  6. data/CHANGELOG.md +24 -0
  7. data/Gemfile +2 -2
  8. data/Gemfile.lock +52 -80
  9. data/auth0.gemspec +2 -2
  10. data/examples/ruby-api/Gemfile +3 -2
  11. data/examples/ruby-api/Gemfile.lock +32 -0
  12. data/examples/ruby-api/README.md +2 -2
  13. data/lib/auth0/api/authentication_endpoints.rb +0 -2
  14. data/lib/auth0/api/v2/connections.rb +6 -3
  15. data/lib/auth0/api/v2/device_credentials.rb +1 -1
  16. data/lib/auth0/api/v2/jobs.rb +15 -4
  17. data/lib/auth0/api/v2/organizations.rb +1 -1
  18. data/lib/auth0/api/v2/users.rb +10 -1
  19. data/lib/auth0/mixins/httpproxy.rb +11 -12
  20. data/lib/auth0/mixins/token_management.rb +1 -1
  21. data/lib/auth0/version.rb +1 -1
  22. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Clients/_patch_client/should_update_the_client_with_the_correct_attributes.yml +2 -1
  23. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Connections/_update_connection/should_update_the_connection.yml +1 -1
  24. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Rules/_update_rule/should_update_the_disabled_rule_to_be_enabled.yml +1 -1
  25. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tenants/_update_tenant_settings/should_revert_the_tenant_name.yml +1 -1
  26. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Tenants/_update_tenant_settings/should_update_the_tenant_settings_with_a_new_tenant_name.yml +1 -1
  27. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_add_user_roles/should_add_a_Role_to_a_User_successfully.yml +1 -1
  28. data/spec/fixtures/vcr_cassettes/Auth0_Api_V2_Users/_patch_user/should_patch_the_User_successfully.yml +1 -1
  29. data/spec/lib/auth0/api/v2/connections_spec.rb +24 -4
  30. data/spec/lib/auth0/api/v2/jobs_spec.rb +18 -0
  31. data/spec/lib/auth0/api/v2/users_spec.rb +15 -0
  32. data/spec/lib/auth0/mixins/httpproxy_spec.rb +53 -17
  33. data/spec/lib/auth0/mixins/token_management_spec.rb +2 -7
  34. data/spec/spec_helper.rb +6 -2
  35. metadata +14 -241
@@ -8,11 +8,12 @@ module Auth0
8
8
  # for now, if you want to feel free to use your own http client
9
9
  module HTTPProxy
10
10
  attr_accessor :headers, :base_uri, :timeout, :retry_count
11
- DEAFULT_RETRIES = 3
11
+ DEFAULT_RETRIES = 3
12
12
  MAX_ALLOWED_RETRIES = 10
13
13
  MAX_REQUEST_RETRY_JITTER = 250
14
14
  MAX_REQUEST_RETRY_DELAY = 1000
15
- MIN_REQUEST_RETRY_DELAY = 100
15
+ MIN_REQUEST_RETRY_DELAY = 250
16
+ BASE_DELAY = 100
16
17
 
17
18
  # proxying requests from instance methods to HTTP class methods
18
19
  %i(get post post_file put patch delete delete_with_body).each do |method|
@@ -26,14 +27,14 @@ module Auth0
26
27
 
27
28
  def retry_options
28
29
  sleep_timer = lambda do |attempt|
29
- wait = 1000 * 2**attempt # Exponential delay with each subsequent request attempt.
30
- wait += rand(wait..wait+MAX_REQUEST_RETRY_JITTER) # Add jitter to the delay window.
30
+ wait = BASE_DELAY * (2**attempt-1) # Exponential delay with each subsequent request attempt.
31
+ wait += rand(wait+1..wait+MAX_REQUEST_RETRY_JITTER) # Add jitter to the delay window.
31
32
  wait = [MAX_REQUEST_RETRY_DELAY, wait].min # Cap delay at MAX_REQUEST_RETRY_DELAY.
32
33
  wait = [MIN_REQUEST_RETRY_DELAY, wait].max # Ensure delay is no less than MIN_REQUEST_RETRY_DELAY.
33
34
  wait / 1000.to_f.round(2) # convert ms to seconds
34
35
  end
35
36
 
36
- tries = 1 + [Integer(retry_count || DEAFULT_RETRIES), MAX_ALLOWED_RETRIES].min # Cap retries at MAX_ALLOWED_RETRIES
37
+ tries = 1 + [Integer(retry_count || DEFAULT_RETRIES), MAX_ALLOWED_RETRIES].min # Cap retries at MAX_ALLOWED_RETRIES
37
38
 
38
39
  {
39
40
  tries: tries,
@@ -72,15 +73,13 @@ module Auth0
72
73
 
73
74
  def request(method, uri, body = {}, extra_headers = {})
74
75
  result = if method == :get
75
- # Mutate the headers property to add parameters.
76
- add_headers({params: body})
77
- # Merge custom headers into existing ones for this req.
78
- # This prevents future calls from using them.
79
- get_headers = headers.merge extra_headers
80
- # Make the call with extra_headers, if provided.
76
+ @headers ||= {}
77
+ get_headers = @headers.merge({params: body}).merge(extra_headers)
81
78
  call(:get, encode_uri(uri), timeout, get_headers)
82
79
  elsif method == :delete
83
- call(:delete, encode_uri(uri), timeout, add_headers({params: body}))
80
+ @headers ||= {}
81
+ delete_headers = @headers.merge({ params: body })
82
+ call(:delete, encode_uri(uri), timeout, delete_headers)
84
83
  elsif method == :delete_with_body
85
84
  call(:delete, encode_uri(uri), timeout, headers, body.to_json)
86
85
  elsif method == :post_file
@@ -6,7 +6,6 @@ module Auth0
6
6
 
7
7
  def initialize_token(options)
8
8
  @token = options[:access_token] || options[:token]
9
-
10
9
  # default expiry to an hour if a token was given but no expires_at
11
10
  @token_expires_at = @token ? options[:token_expires_at] || Time.now.to_i + 3600 : nil
12
11
 
@@ -15,6 +14,7 @@ module Auth0
15
14
  end
16
15
 
17
16
  def get_token
17
+ # pp @token_expires_at
18
18
  has_expired = @token && @token_expires_at ? @token_expires_at < (Time.now.to_i + 10) : false
19
19
 
20
20
  if (@token.nil? || has_expired) && @client_id && @client_secret
data/lib/auth0/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # current version of gem
2
2
  module Auth0
3
- VERSION = '5.8.1'.freeze
3
+ VERSION = '5.10.0'.freeze
4
4
  end
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: patch
5
- uri: https://auth0-sdk-tests.auth0.com/api/v2/clients/SftKo9ySyHnMPezQUFd0C70GBoNFM21F?fields=jwt_configuration&include_fields=false
5
+ uri: https://auth0-sdk-tests.auth0.com/api/v2/clients/SftKo9ySyHnMPezQUFd0C70GBoNFM21F
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"custom_login_page_on":false,"sso":true}'
@@ -12,6 +12,7 @@ http_interactions:
12
12
  User-Agent:
13
13
  - rest-client/2.1.0 (darwin19.6.0 x86_64) ruby/2.7.0p0
14
14
  Content-Type:
15
+
15
16
  - application/json
16
17
  Auth0-Client:
17
18
  - eyJuYW1lIjoicnVieS1hdXRoMCIsInZlcnNpb24iOiI1LjUuMCIsImVudiI6eyJydWJ5IjoiMi43LjAifX0=
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: patch
5
- uri: https://auth0-sdk-tests.auth0.com/api/v2/connections/con_WltM0fv20JCnxOuY?email=rubytest-210908-rubytest-210908-username@auth0.com
5
+ uri: https://auth0-sdk-tests.auth0.com/api/v2/connections/con_WltM0fv20JCnxOuY
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"options":{"mfa":{"active":true,"return_enroll_settings":true},"passwordPolicy":"excellent","strategy_version":2,"brute_force_protection":true}}'
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: patch
5
- uri: https://auth0-sdk-tests.auth0.com/api/v2/rules/rul_bsg64xEPZz4WOkXz?fields=stage&include_fields=false
5
+ uri: https://auth0-sdk-tests.auth0.com/api/v2/rules/rul_bsg64xEPZz4WOkXz
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"enabled":true}'
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: patch
5
- uri: https://auth0-sdk-tests.auth0.com/api/v2/tenants/settings?fields=support_email&include_fields=true
5
+ uri: https://auth0-sdk-tests.auth0.com/api/v2/tenants/settings
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"friendly_name":"Auth0"}'
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: patch
5
- uri: https://auth0-sdk-tests.auth0.com/api/v2/tenants/settings?fields=support_email&include_fields=true
5
+ uri: https://auth0-sdk-tests.auth0.com/api/v2/tenants/settings
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"friendly_name":"Auth0-CHANGED"}'
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: https://auth0-sdk-tests.auth0.com/api/v2/users/auth0%7C613282adac819400692c0dd9/roles?per_page=2
5
+ uri: https://auth0-sdk-tests.auth0.com/api/v2/users/auth0%7C613282adac819400692c0dd9/roles
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"roles":["rol_2VZOCes8HgBar3Tp"]}'
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: patch
5
- uri: https://auth0-sdk-tests.auth0.com/api/v2/users/auth0%7C613282adac819400692c0dd9?fields=email&include_fields=true
5
+ uri: https://auth0-sdk-tests.auth0.com/api/v2/users/auth0%7C613282adac819400692c0dd9
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"email_verified":true,"user_metadata":{"addresses":{"home_address":"742
@@ -18,7 +18,8 @@ describe Auth0::Api::V2::Connections do
18
18
  fields: nil,
19
19
  include_fields: nil,
20
20
  page: nil,
21
- per_page: nil
21
+ per_page: nil,
22
+ include_totals: nil
22
23
  })
23
24
  expect { @instance.connections }.not_to raise_error
24
25
  end
@@ -31,7 +32,8 @@ describe Auth0::Api::V2::Connections do
31
32
  strategy: nil,
32
33
  name: nil,
33
34
  page: nil,
34
- per_page: nil
35
+ per_page: nil,
36
+ include_totals: nil
35
37
  })
36
38
  expect {
37
39
  @instance.connections(fields: 'name', include_fields: true)
@@ -46,7 +48,8 @@ describe Auth0::Api::V2::Connections do
46
48
  strategy: nil,
47
49
  name: nil,
48
50
  page: nil,
49
- per_page: nil
51
+ per_page: nil,
52
+ include_totals: nil
50
53
  })
51
54
  expect {
52
55
  @instance.connections(fields: ['name','strategy'], include_fields: true)
@@ -61,12 +64,29 @@ describe Auth0::Api::V2::Connections do
61
64
  strategy: nil,
62
65
  name: nil,
63
66
  fields: nil,
64
- include_fields: nil
67
+ include_fields: nil,
68
+ include_totals: nil
65
69
  })
66
70
  expect {
67
71
  @instance.connections(page: 1, per_page: 10)
68
72
  }.not_to raise_error
69
73
  end
74
+
75
+ it 'is expected to include totals' do
76
+ expect(@instance).to receive(:get).with(
77
+ '/api/v2/connections', {
78
+ page: 1,
79
+ per_page: 10,
80
+ strategy: nil,
81
+ name: nil,
82
+ fields: nil,
83
+ include_fields: nil,
84
+ include_totals: true
85
+ })
86
+ expect {
87
+ @instance.connections(page: 1, per_page: 10, include_totals: true)
88
+ }.not_to raise_error
89
+ end
70
90
  end
71
91
 
72
92
  context '.create_connection' do
@@ -67,6 +67,7 @@ describe Auth0::Api::V2::Jobs do
67
67
  format: 'csv',
68
68
  limit: 10
69
69
  })
70
+
70
71
  @instance.export_users(
71
72
  fields: ['author'],
72
73
  connection_id: 'test-connection',
@@ -74,6 +75,23 @@ describe Auth0::Api::V2::Jobs do
74
75
  limit: 10
75
76
  )
76
77
  end
78
+
79
+ it 'sends post to /api/v2/jobs/users-exports with export_as field' do
80
+ expect(@instance).to receive(:post).with(
81
+ '/api/v2/jobs/users-exports', {
82
+ fields: [{ name: 'author', export_as: 'writer' }],
83
+ connection_id: 'test-connection',
84
+ format: 'csv',
85
+ limit: 10
86
+ })
87
+
88
+ @instance.export_users(
89
+ fields: [{ name: 'author', export_as: 'writer' }],
90
+ connection_id: 'test-connection',
91
+ format: 'csv',
92
+ limit: 10
93
+ )
94
+ end
77
95
  end
78
96
 
79
97
  context '.send_verification_email' do
@@ -139,6 +139,21 @@ describe Auth0::Api::V2::Users do
139
139
  end
140
140
  end
141
141
 
142
+ context '.delete_user_authenticators' do
143
+ it 'is expected to respond to a delete_user_authenticators method' do
144
+ expect(@instance).to respond_to(:delete_user_authenticators)
145
+ end
146
+
147
+ it 'is expected to delete /api/v2/users/userId/authenticators' do
148
+ expect(@instance).to receive(:delete).with('/api/v2/users/USER_ID/authenticators')
149
+ @instance.delete_user_authenticators('USER_ID')
150
+ end
151
+
152
+ it 'is expected to raise an exception when the user ID is empty' do
153
+ expect { @instance.delete_user_authenticators(nil) }.to raise_exception(Auth0::MissingUserId)
154
+ end
155
+ end
156
+
142
157
  context '.delete_user_provider' do
143
158
  it 'is expected to respond to a delete_user_provider method' do
144
159
  expect(@instance).to respond_to(:delete_user_provider)
@@ -494,12 +494,13 @@ describe Auth0::Mixins::HTTPProxy do
494
494
  end
495
495
 
496
496
  context "Renewing tokens" do
497
- before :each do
498
- @token_instance = DummyClassForTokens.new(
497
+ let(:httpproxy_instance) {
498
+ DummyClassForTokens.new(
499
499
  client_id: 'test-client-id',
500
500
  client_secret: 'test-client-secret',
501
- domain: 'auth0.com')
502
- end
501
+ domain: 'auth0.com',
502
+ )
503
+ }
503
504
 
504
505
  %i(get delete).each do |http_method|
505
506
  context "for #{http_method}" do
@@ -507,7 +508,7 @@ describe Auth0::Mixins::HTTPProxy do
507
508
  expect(RestClient::Request).to receive(:execute).with(hash_including(
508
509
  method: :post,
509
510
  url: 'https://auth0.com/oauth/token',
510
- ) ).and_return(StubResponse.new({
511
+ )).and_return(StubResponse.new({
511
512
  "access_token" => "access_token",
512
513
  "expires_in" => 86400},
513
514
  true,
@@ -515,11 +516,10 @@ describe Auth0::Mixins::HTTPProxy do
515
516
 
516
517
  expect(RestClient::Request).to receive(:execute).with(hash_including(
517
518
  method: http_method,
518
- url: 'https://auth0.com/test',
519
- headers: { params: {}, "Authorization" => "Bearer access_token" }
519
+ url: 'https://auth0.com/test'
520
520
  )).and_return(StubResponse.new('Some random text here', true, 200))
521
521
 
522
- expect { @token_instance.send(http_method, '/test') }.not_to raise_error
522
+ expect { httpproxy_instance.send(http_method, '/test') }.not_to raise_error
523
523
  end
524
524
  end
525
525
  end
@@ -539,24 +539,24 @@ describe Auth0::Mixins::HTTPProxy do
539
539
  expect(RestClient::Request).to receive(:execute).with(hash_including(
540
540
  method: http_method,
541
541
  url: 'https://auth0.com/test',
542
- headers: { "Authorization" => "Bearer access_token" }
542
+ headers: hash_including( "Authorization" => "Bearer access_token")
543
543
  )).and_return(StubResponse.new('Some random text here', true, 200))
544
544
 
545
- expect { @token_instance.send(http_method, '/test') }.not_to raise_error
545
+ expect { httpproxy_instance.send(http_method, '/test') }.not_to raise_error
546
546
  end
547
547
  end
548
548
  end
549
549
  end
550
550
 
551
551
  context "Using cached tokens" do
552
- before :each do
553
- @token_instance = DummyClassForTokens.new(
552
+ let(:httpproxy_instance) {
553
+ DummyClassForTokens.new(
554
554
  client_id: 'test-client-id',
555
555
  client_secret: 'test-client-secret',
556
556
  domain: 'auth0.com',
557
557
  token: 'access_token',
558
558
  token_expires_at: Time.now.to_i + 86400)
559
- end
559
+ }
560
560
 
561
561
  %i(get delete).each do |http_method|
562
562
  context "for #{http_method}" do
@@ -569,10 +569,10 @@ describe Auth0::Mixins::HTTPProxy do
569
569
  expect(RestClient::Request).to receive(:execute).with(hash_including(
570
570
  method: http_method,
571
571
  url: 'https://auth0.com/test',
572
- headers: { params: {}, "Authorization" => "Bearer access_token" }
572
+ headers: hash_including(params: {}, "Authorization" => "Bearer access_token")
573
573
  )).and_return(StubResponse.new('Some random text here', true, 200))
574
574
 
575
- expect { @token_instance.send(http_method, '/test') }.not_to raise_error
575
+ expect { httpproxy_instance.send(http_method, '/test') }.not_to raise_error
576
576
  end
577
577
  end
578
578
  end
@@ -588,10 +588,46 @@ describe Auth0::Mixins::HTTPProxy do
588
588
  expect(RestClient::Request).to receive(:execute).with(hash_including(
589
589
  method: http_method,
590
590
  url: 'https://auth0.com/test',
591
- headers: { "Authorization" => "Bearer access_token" }
591
+ headers: hash_including("Authorization" => "Bearer access_token")
592
592
  )).and_return(StubResponse.new('Some random text here', true, 200))
593
593
 
594
- expect { @token_instance.send(http_method, '/test') }.not_to raise_error
594
+ expect { httpproxy_instance.send(http_method, '/test') }.not_to raise_error
595
+ end
596
+ end
597
+ end
598
+ end
599
+
600
+ context 'Normal operation' do
601
+ let(:httpproxy_instance) {
602
+ DummyClassForTokens.new(
603
+ client_id: 'test-client-id',
604
+ client_secret: 'test-client-secret',
605
+ domain: 'auth0.com',
606
+ token: 'access_token',
607
+ token_expires_at: Time.now.to_i + 86400)
608
+ }
609
+
610
+ # This sets up a test matrix to verify that both :get and :delete calls (the only two HTTP methods in the proxy that mutated headers)
611
+ # don't bleed query params into subsequent calls to :post :patch and :put.
612
+ %i(get delete).each do |http_get_delete|
613
+ %i(post patch put).each do |http_ppp|
614
+ it "should not bleed :#{http_get_delete} headers/parameters to the subsequent :#{http_ppp} request" do
615
+ expect(RestClient::Request).to receive(:execute).with(hash_including(
616
+ method: http_get_delete,
617
+ url: "https://auth0.com/test-#{http_get_delete}",
618
+ headers: hash_including(params: { email: 'test@test.com' })
619
+ )).and_return(StubResponse.new('OK', true, 200))
620
+
621
+ # email: parameter that is sent in the GET request should not appear
622
+ # as a parameter in the `headers` hash for the subsequent PATCH request.
623
+ expect(RestClient::Request).to receive(:execute).with(hash_including(
624
+ method: http_ppp,
625
+ url: "https://auth0.com/test-#{http_ppp}",
626
+ headers: hash_not_including(:params)
627
+ )).and_return(StubResponse.new('OK', true, 200))
628
+
629
+ expect { httpproxy_instance.send(http_get_delete, "/test-#{http_get_delete}", { email: 'test@test.com' }) }.not_to raise_error
630
+ expect { httpproxy_instance.send(http_ppp, "/test-#{http_ppp}") }.not_to raise_error
595
631
  end
596
632
  end
597
633
  end
@@ -110,16 +110,11 @@ describe Auth0::Mixins::TokenManagement do
110
110
 
111
111
  it 'does not renew existing token if no token_expires_at' do
112
112
  params[:token] = 'test-token'
113
+ instance.instance_variable_set '@token_expires_at', nil
113
114
 
114
- expect(RestClient::Request).not_to receive(:execute).with(hash_including(
115
- method: :post,
116
- url: 'https://samples.auth0.com/oauth/token',
117
- ))
115
+ expect(RestClient::Request).not_to receive(:execute)
118
116
 
119
117
  instance.send(:get_token)
120
-
121
- expect(instance.instance_variable_get('@token')).to eq('test-token')
122
- expect(instance.instance_variable_get('@token_expires_at')).to be_nil
123
118
  end
124
119
  end
125
120
  end
data/spec/spec_helper.rb CHANGED
@@ -13,8 +13,8 @@ require 'simplecov'
13
13
  SimpleCov.start
14
14
 
15
15
  if ENV['CI'] == 'true'
16
- require 'codecov'
17
- SimpleCov.formatter = SimpleCov::Formatter::Codecov
16
+ require 'simplecov-cobertura'
17
+ SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
18
18
  end
19
19
 
20
20
  require 'dotenv'
@@ -51,6 +51,10 @@ RSpec.configure do |config|
51
51
  config.filter_run focus: true
52
52
  config.run_all_when_everything_filtered = true
53
53
  config.include Credentials
54
+
55
+ config.expect_with :rspec do |c|
56
+ c.max_formatted_output_length = 1000000
57
+ end
54
58
  end
55
59
 
56
60
  def wait(time, increment = 5, elapsed_time = 0, &block)