auth0 5.8.1 → 5.10.0

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.
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)