descope 1.0.4

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 (197) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yaml +54 -0
  3. data/.gitignore +59 -0
  4. data/.release-please-manifest.json +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/.rubocop_todo.yml +10 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.md +90 -0
  9. data/Gemfile +22 -0
  10. data/Gemfile.lock +204 -0
  11. data/LICENSE +21 -0
  12. data/README.md +1171 -0
  13. data/Rakefile +31 -0
  14. data/descope.gemspec +34 -0
  15. data/examples/ruby/Gemfile +4 -0
  16. data/examples/ruby/Gemfile.lock +41 -0
  17. data/examples/ruby/access_key_app.rb +45 -0
  18. data/examples/ruby/enchantedlink_app.rb +65 -0
  19. data/examples/ruby/magiclink_app.rb +81 -0
  20. data/examples/ruby/management/Gemfile +5 -0
  21. data/examples/ruby/management/Gemfile.lock +38 -0
  22. data/examples/ruby/management/access_key_app.rb +71 -0
  23. data/examples/ruby/management/audit_app.rb +25 -0
  24. data/examples/ruby/management/authz_app.rb +135 -0
  25. data/examples/ruby/management/authz_files.json +229 -0
  26. data/examples/ruby/management/flow_app.rb +57 -0
  27. data/examples/ruby/management/permission_app.rb +56 -0
  28. data/examples/ruby/management/role_app.rb +58 -0
  29. data/examples/ruby/management/tenant_app.rb +60 -0
  30. data/examples/ruby/management/user_app.rb +60 -0
  31. data/examples/ruby/oauth_app.rb +39 -0
  32. data/examples/ruby/otp_app.rb +50 -0
  33. data/examples/ruby/password_app.rb +76 -0
  34. data/examples/ruby/saml_app.rb +38 -0
  35. data/examples/ruby-on-rails-api/descope/.dockerignore +37 -0
  36. data/examples/ruby-on-rails-api/descope/.gitattributes +9 -0
  37. data/examples/ruby-on-rails-api/descope/.gitignore +40 -0
  38. data/examples/ruby-on-rails-api/descope/.node-version +1 -0
  39. data/examples/ruby-on-rails-api/descope/.ruby-version +1 -0
  40. data/examples/ruby-on-rails-api/descope/Dockerfile +75 -0
  41. data/examples/ruby-on-rails-api/descope/Gemfile +67 -0
  42. data/examples/ruby-on-rails-api/descope/Gemfile.lock +284 -0
  43. data/examples/ruby-on-rails-api/descope/Procfile.dev +3 -0
  44. data/examples/ruby-on-rails-api/descope/README.md +54 -0
  45. data/examples/ruby-on-rails-api/descope/Rakefile +6 -0
  46. data/examples/ruby-on-rails-api/descope/app/assets/builds/.keep +0 -0
  47. data/examples/ruby-on-rails-api/descope/app/assets/config/manifest.js +3 -0
  48. data/examples/ruby-on-rails-api/descope/app/assets/images/.keep +0 -0
  49. data/examples/ruby-on-rails-api/descope/app/assets/images/descope.jpeg +0 -0
  50. data/examples/ruby-on-rails-api/descope/app/assets/images/favicon.ico +0 -0
  51. data/examples/ruby-on-rails-api/descope/app/assets/images/logo192.png +0 -0
  52. data/examples/ruby-on-rails-api/descope/app/assets/images/logo512.png +0 -0
  53. data/examples/ruby-on-rails-api/descope/app/assets/stylesheets/application.bootstrap.scss +67 -0
  54. data/examples/ruby-on-rails-api/descope/app/channels/application_cable/channel.rb +4 -0
  55. data/examples/ruby-on-rails-api/descope/app/channels/application_cable/connection.rb +4 -0
  56. data/examples/ruby-on-rails-api/descope/app/controllers/application_controller.rb +2 -0
  57. data/examples/ruby-on-rails-api/descope/app/controllers/concerns/.keep +0 -0
  58. data/examples/ruby-on-rails-api/descope/app/controllers/homepage_controller.rb +4 -0
  59. data/examples/ruby-on-rails-api/descope/app/controllers/session_controller.rb +66 -0
  60. data/examples/ruby-on-rails-api/descope/app/helpers/application_helper.rb +2 -0
  61. data/examples/ruby-on-rails-api/descope/app/helpers/homepage_helper.rb +2 -0
  62. data/examples/ruby-on-rails-api/descope/app/helpers/session_helper.rb +2 -0
  63. data/examples/ruby-on-rails-api/descope/app/javascript/App.css +53 -0
  64. data/examples/ruby-on-rails-api/descope/app/javascript/application.js +5 -0
  65. data/examples/ruby-on-rails-api/descope/app/javascript/components/App.jsx +4 -0
  66. data/examples/ruby-on-rails-api/descope/app/javascript/components/Dashboard.jsx +60 -0
  67. data/examples/ruby-on-rails-api/descope/app/javascript/components/Home.jsx +27 -0
  68. data/examples/ruby-on-rails-api/descope/app/javascript/components/Login.jsx +45 -0
  69. data/examples/ruby-on-rails-api/descope/app/javascript/components/Profile.jsx +81 -0
  70. data/examples/ruby-on-rails-api/descope/app/javascript/components/index.html +11 -0
  71. data/examples/ruby-on-rails-api/descope/app/javascript/components/index.jsx +24 -0
  72. data/examples/ruby-on-rails-api/descope/app/javascript/controllers/application.js +9 -0
  73. data/examples/ruby-on-rails-api/descope/app/javascript/controllers/index.js +5 -0
  74. data/examples/ruby-on-rails-api/descope/app/javascript/reportWebVitals.js +13 -0
  75. data/examples/ruby-on-rails-api/descope/app/javascript/routes/index.jsx +17 -0
  76. data/examples/ruby-on-rails-api/descope/app/jobs/application_job.rb +7 -0
  77. data/examples/ruby-on-rails-api/descope/app/mailers/application_mailer.rb +4 -0
  78. data/examples/ruby-on-rails-api/descope/app/models/application_record.rb +3 -0
  79. data/examples/ruby-on-rails-api/descope/app/models/concerns/.keep +0 -0
  80. data/examples/ruby-on-rails-api/descope/app/views/homepage/index.html.erb +2 -0
  81. data/examples/ruby-on-rails-api/descope/app/views/layouts/application.html.erb +16 -0
  82. data/examples/ruby-on-rails-api/descope/app/views/layouts/mailer.html.erb +13 -0
  83. data/examples/ruby-on-rails-api/descope/app/views/layouts/mailer.text.erb +1 -0
  84. data/examples/ruby-on-rails-api/descope/app/views/session/index.html.erb +2 -0
  85. data/examples/ruby-on-rails-api/descope/bin/bundle +109 -0
  86. data/examples/ruby-on-rails-api/descope/bin/dev +11 -0
  87. data/examples/ruby-on-rails-api/descope/bin/docker-entrypoint +8 -0
  88. data/examples/ruby-on-rails-api/descope/bin/rails +4 -0
  89. data/examples/ruby-on-rails-api/descope/bin/rake +4 -0
  90. data/examples/ruby-on-rails-api/descope/bin/setup +36 -0
  91. data/examples/ruby-on-rails-api/descope/build.js +30 -0
  92. data/examples/ruby-on-rails-api/descope/config/application.rb +42 -0
  93. data/examples/ruby-on-rails-api/descope/config/boot.rb +4 -0
  94. data/examples/ruby-on-rails-api/descope/config/cable.yml +10 -0
  95. data/examples/ruby-on-rails-api/descope/config/config.yml +9 -0
  96. data/examples/ruby-on-rails-api/descope/config/credentials.yml.enc +1 -0
  97. data/examples/ruby-on-rails-api/descope/config/database.yml +25 -0
  98. data/examples/ruby-on-rails-api/descope/config/environment.rb +5 -0
  99. data/examples/ruby-on-rails-api/descope/config/environments/development.rb +76 -0
  100. data/examples/ruby-on-rails-api/descope/config/environments/production.rb +97 -0
  101. data/examples/ruby-on-rails-api/descope/config/environments/test.rb +64 -0
  102. data/examples/ruby-on-rails-api/descope/config/initializers/assets.rb +13 -0
  103. data/examples/ruby-on-rails-api/descope/config/initializers/content_security_policy.rb +25 -0
  104. data/examples/ruby-on-rails-api/descope/config/initializers/filter_parameter_logging.rb +8 -0
  105. data/examples/ruby-on-rails-api/descope/config/initializers/inflections.rb +16 -0
  106. data/examples/ruby-on-rails-api/descope/config/initializers/load_config.rb +12 -0
  107. data/examples/ruby-on-rails-api/descope/config/initializers/permissions_policy.rb +13 -0
  108. data/examples/ruby-on-rails-api/descope/config/locales/en.yml +31 -0
  109. data/examples/ruby-on-rails-api/descope/config/puma.rb +35 -0
  110. data/examples/ruby-on-rails-api/descope/config/routes.rb +18 -0
  111. data/examples/ruby-on-rails-api/descope/config/storage.yml +34 -0
  112. data/examples/ruby-on-rails-api/descope/config.ru +6 -0
  113. data/examples/ruby-on-rails-api/descope/db/seeds.rb +9 -0
  114. data/examples/ruby-on-rails-api/descope/lib/assets/.keep +0 -0
  115. data/examples/ruby-on-rails-api/descope/lib/tasks/.keep +0 -0
  116. data/examples/ruby-on-rails-api/descope/log/.keep +0 -0
  117. data/examples/ruby-on-rails-api/descope/package-lock.json +19680 -0
  118. data/examples/ruby-on-rails-api/descope/package.json +51 -0
  119. data/examples/ruby-on-rails-api/descope/public/404.html +67 -0
  120. data/examples/ruby-on-rails-api/descope/public/422.html +67 -0
  121. data/examples/ruby-on-rails-api/descope/public/500.html +66 -0
  122. data/examples/ruby-on-rails-api/descope/public/apple-touch-icon-precomposed.png +0 -0
  123. data/examples/ruby-on-rails-api/descope/public/apple-touch-icon.png +0 -0
  124. data/examples/ruby-on-rails-api/descope/public/favicon.ico +0 -0
  125. data/examples/ruby-on-rails-api/descope/public/robots.txt +1 -0
  126. data/examples/ruby-on-rails-api/descope/storage/.keep +0 -0
  127. data/examples/ruby-on-rails-api/descope/tmp/.keep +0 -0
  128. data/examples/ruby-on-rails-api/descope/tmp/pids/.keep +0 -0
  129. data/examples/ruby-on-rails-api/descope/tmp/storage/.keep +0 -0
  130. data/examples/ruby-on-rails-api/descope/vendor/.keep +0 -0
  131. data/examples/ruby-on-rails-api/descope/yarn.lock +10780 -0
  132. data/lib/descope/api/v1/auth/enchantedlink.rb +156 -0
  133. data/lib/descope/api/v1/auth/magiclink.rb +170 -0
  134. data/lib/descope/api/v1/auth/oauth.rb +72 -0
  135. data/lib/descope/api/v1/auth/otp.rb +186 -0
  136. data/lib/descope/api/v1/auth/password.rb +100 -0
  137. data/lib/descope/api/v1/auth/saml.rb +48 -0
  138. data/lib/descope/api/v1/auth/totp.rb +72 -0
  139. data/lib/descope/api/v1/auth.rb +452 -0
  140. data/lib/descope/api/v1/management/access_key.rb +81 -0
  141. data/lib/descope/api/v1/management/audit.rb +82 -0
  142. data/lib/descope/api/v1/management/authz.rb +165 -0
  143. data/lib/descope/api/v1/management/common.rb +147 -0
  144. data/lib/descope/api/v1/management/flow.rb +55 -0
  145. data/lib/descope/api/v1/management/password.rb +58 -0
  146. data/lib/descope/api/v1/management/permission.rb +48 -0
  147. data/lib/descope/api/v1/management/project.rb +53 -0
  148. data/lib/descope/api/v1/management/role.rb +48 -0
  149. data/lib/descope/api/v1/management/scim.rb +206 -0
  150. data/lib/descope/api/v1/management/sso_settings.rb +153 -0
  151. data/lib/descope/api/v1/management/tenant.rb +71 -0
  152. data/lib/descope/api/v1/management/user.rb +619 -0
  153. data/lib/descope/api/v1/management.rb +38 -0
  154. data/lib/descope/api/v1/session.rb +84 -0
  155. data/lib/descope/api/v1.rb +13 -0
  156. data/lib/descope/client.rb +6 -0
  157. data/lib/descope/exception.rb +50 -0
  158. data/lib/descope/mixins/common.rb +129 -0
  159. data/lib/descope/mixins/headers.rb +15 -0
  160. data/lib/descope/mixins/http.rb +133 -0
  161. data/lib/descope/mixins/initializer.rb +80 -0
  162. data/lib/descope/mixins/logging.rb +30 -0
  163. data/lib/descope/mixins/validation.rb +79 -0
  164. data/lib/descope/mixins.rb +22 -0
  165. data/lib/descope/version.rb +7 -0
  166. data/lib/descope.rb +9 -0
  167. data/lib/descope_client.rb +5 -0
  168. data/release-please-config.json +18 -0
  169. data/renovate.json +6 -0
  170. data/spec/factories/user.rb +16 -0
  171. data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +159 -0
  172. data/spec/lib.descope/api/v1/auth/magiclink_spec.rb +282 -0
  173. data/spec/lib.descope/api/v1/auth/oauth_spec.rb +117 -0
  174. data/spec/lib.descope/api/v1/auth/otp_spec.rb +285 -0
  175. data/spec/lib.descope/api/v1/auth/password_spec.rb +124 -0
  176. data/spec/lib.descope/api/v1/auth/saml_spec.rb +55 -0
  177. data/spec/lib.descope/api/v1/auth/totp_spec.rb +70 -0
  178. data/spec/lib.descope/api/v1/auth_spec.rb +372 -0
  179. data/spec/lib.descope/api/v1/management/access_key_spec.rb +118 -0
  180. data/spec/lib.descope/api/v1/management/audit_spec.rb +78 -0
  181. data/spec/lib.descope/api/v1/management/authz_spec.rb +336 -0
  182. data/spec/lib.descope/api/v1/management/flow_spec.rb +78 -0
  183. data/spec/lib.descope/api/v1/management/password_spec.rb +25 -0
  184. data/spec/lib.descope/api/v1/management/permission_spec.rb +81 -0
  185. data/spec/lib.descope/api/v1/management/project_spec.rb +63 -0
  186. data/spec/lib.descope/api/v1/management/role_spec.rb +85 -0
  187. data/spec/lib.descope/api/v1/management/scim_spec.rb +312 -0
  188. data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +172 -0
  189. data/spec/lib.descope/api/v1/management/tenant_spec.rb +141 -0
  190. data/spec/lib.descope/api/v1/management/user_spec.rb +667 -0
  191. data/spec/lib.descope/api/v1/session_spec.rb +117 -0
  192. data/spec/lib.descope/client_spec.rb +40 -0
  193. data/spec/spec_helper.rb +72 -0
  194. data/spec/support/client_config.rb +14 -0
  195. data/spec/support/dummy_class.rb +36 -0
  196. data/spec/support/utils.rb +32 -0
  197. metadata +420 -0
@@ -0,0 +1,372 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Descope::Api::V1::Auth do
6
+ before(:all) do
7
+ dummy_instance = DummyClass.new
8
+ dummy_instance.extend(Descope::Api::V1::Auth)
9
+ dummy_instance.extend(Descope::Api::V1::Session)
10
+ dummy_instance.extend(Descope::Mixins::Common::EndpointsV1)
11
+ @instance = dummy_instance
12
+ end
13
+
14
+ LEEWAY = 10
15
+ CLOCK = Time.now.to_i
16
+ ALGORITHM = 'RS256'.freeze
17
+ CONTEXT = { algorithm: ALGORITHM, leeway: LEEWAY, audience: 'tokens-test-123',
18
+ issuer: 'https://tokens-test.descope.com/', clock: CLOCK }.freeze
19
+
20
+ let(:public_key) do
21
+ {
22
+ 'alg' => 'RS256',
23
+ 'e' => 'AQAB',
24
+ 'kid' => 'SK2ZoKi2Q4I5U8SjammTyOXRWvKl0',
25
+ 'kty' => 'RSA',
26
+ 'n' => 'shNRO_U_YXlYVNC-dXi49tt5Za3aUVhdC59TqYAlNIaD-nnnQzF__MuEtROzEzWDISXpmAxMcK0zvELikeSVjzjO8KiTrv29sx-Srt8nd9t7wvT8YE93X2U3HOqBXs5a4MXhnaBfzEQPgwnysDGT0HSeqpTLDJ3wvwkvpAOANqAudcgjDbfWAp59WBJWrfM8WSYNAt_NGSrqVJomWIFrwTwmYkn6Fs2bvu6y4TmZJwqfvGklGA6tV3vXwVTWzEYhSybo5CfODu6bHGP9KpXlvNIpf4eJ80i5WjI2GCYx88D3l79p0rEdP2zRr_a45gfJO3dz4DmHVBAlu1M0IIq6DQ',
27
+ 'use' => 'sig'
28
+ }
29
+ end
30
+
31
+ context 'validate_and_load_public_key' do
32
+ it 'is expected to validate and load public key' do
33
+ expect { @instance.send(:validate_and_load_public_key, ['some_key']) }.to raise_error(Descope::AuthException)
34
+ end
35
+
36
+ it 'is expected to fail parsing bad public key JSON' do
37
+ expect do
38
+ @instance.send(:validate_and_load_public_key, '{ this: is not valid JSON')
39
+ end.to raise_error(
40
+ Descope::AuthException, /Unable to parse public key json, error/
41
+ )
42
+ end
43
+
44
+ it 'is expected to fail on missing alg property' do
45
+ expect do
46
+ @instance.send(:validate_and_load_public_key, { 'kid' => 'some_kid' })
47
+ end.to raise_error(
48
+ Descope::AuthException, /Unable to load public key. Missing property: alg/
49
+ )
50
+ end
51
+
52
+ it 'is expected to fail on missing kid property' do
53
+ expect do
54
+ @instance.send(:validate_and_load_public_key, { 'alg' => 'some_alg' })
55
+ end.to raise_error(
56
+ Descope::AuthException, /Unable to load public key. Missing property: kid/
57
+ )
58
+ end
59
+
60
+ it 'is expected to load with correct public key' do
61
+ returned_value = nil
62
+
63
+ expect do
64
+ returned_value = @instance.send(:validate_and_load_public_key, public_key)
65
+ end.not_to raise_error
66
+
67
+ expected_value = [public_key['kid'], JWT::JWK.new(public_key), public_key['alg']]
68
+ expect(returned_value).to eq(expected_value)
69
+ end
70
+ end
71
+
72
+ context 'fetch_public_keys' do
73
+ it 'is expected to fetch public keys' do
74
+ allow(@instance).to receive(:token_validation_key).and_return({ 'keys' => [public_key] })
75
+ expect { @instance.send(:fetch_public_keys) }.not_to raise_error
76
+ expect(@instance.instance_variable_get(:@public_keys)).not_to be_nil
77
+ expect(@instance.instance_variable_get(:@public_keys)).to eq(
78
+ { public_key['kid'] => [JWT::JWK.new(public_key), public_key['alg']] }
79
+ )
80
+ end
81
+ end
82
+
83
+ context 'jwt_get_unverified_header' do
84
+ it 'is expected to fail parsing bad token' do
85
+ expect do
86
+ @instance.send(:jwt_get_unverified_header, 'bad_token')
87
+ end.to raise_error(
88
+ Descope::AuthException, /Unable to parse token/
89
+ )
90
+ end
91
+
92
+ it 'is expected to return header' do
93
+ header = { 'some_header' => 'some_value' }
94
+ token = JWT.encode({}, nil, 'none', header)
95
+ expect(@instance.send(:jwt_get_unverified_header, token)).to eq(header.merge('alg' => 'none'))
96
+ end
97
+ end
98
+
99
+ context 'validate_token' do
100
+ it 'is expected to fail on missing token' do
101
+ expect do
102
+ @instance.send(:validate_token, nil)
103
+ end.to raise_error(Descope::AuthException, /Token validation received empty token/)
104
+ expect do
105
+ @instance.send(:validate_token, '')
106
+ end.to raise_error(Descope::AuthException, /Token validation received empty token/)
107
+ end
108
+
109
+ it 'is expected to fail on missing alg property' do
110
+ allow_any_instance_of(Descope::Api::V1::Auth).to receive(:jwt_get_unverified_header).and_return({ 'alg' => 'none' })
111
+ expect do
112
+ @instance.send(:validate_token, 'some_token')
113
+ end.to raise_error(Descope::AuthException, /Token header is missing property: alg/)
114
+ end
115
+
116
+ it 'is expected to fail on missing kid property' do
117
+ allow_any_instance_of(Descope::Api::V1::Auth).to receive(:jwt_get_unverified_header).and_return({ 'alg' => 'RS256' })
118
+ expect do
119
+ @instance.send(:validate_token, 'some_token')
120
+ end.to raise_error(Descope::AuthException, /Token header is missing property: kid/)
121
+ end
122
+
123
+ it 'is expected to fail on missing public key after fetch_public_keys is called' do
124
+ allow_any_instance_of(Descope::Api::V1::Auth).to receive(:jwt_get_unverified_header).and_return(
125
+ {
126
+ 'alg' => 'RS256',
127
+ 'kid' => 'some_kid'
128
+ }
129
+ )
130
+ allow_any_instance_of(Descope::Api::V1::Auth).to receive(:fetch_public_keys).and_return(nil)
131
+ expect do
132
+ @instance.send(:validate_token, 'some_token')
133
+ end.to raise_error(Descope::AuthException, /Unable to validate public key. Public key not found./)
134
+ end
135
+
136
+ it 'is expected to fail when alg_header != alg_from_key' do
137
+ allow_any_instance_of(Descope::Api::V1::Auth).to receive(:jwt_get_unverified_header).and_return(
138
+ {
139
+ 'alg' => 'RS384',
140
+ 'kid' => 'some_kid'
141
+ }
142
+ )
143
+ # bypassing fetch_public_keys since all we need is the set of public_keys attribute on the client class
144
+ allow_any_instance_of(Descope::Api::V1::Auth).to receive(:fetch_public_keys).and_return(nil)
145
+ expect(
146
+ @instance.instance_variable_set(
147
+ :@public_keys, { 'some_kid' => [JWT::JWK.new(public_key), public_key['alg']] }
148
+ )
149
+ )
150
+
151
+ expect do
152
+ @instance.send(:validate_token, 'some_token')
153
+ end.to raise_error(
154
+ Descope::AuthException,
155
+ /Algorithm signature in JWT header does not match the algorithm signature in the Public key./
156
+ )
157
+ end
158
+
159
+ it 'is expected to decode the token successfully' do
160
+ rsa_private = OpenSSL::PKey::RSA.generate 2048
161
+ rsa_public = rsa_private.public_key
162
+
163
+ allow_any_instance_of(Descope::Api::V1::Auth).to receive(:jwt_get_unverified_header).and_return(
164
+ {
165
+ 'alg' => 'RS256',
166
+ 'kid' => 'some_kid'
167
+ }
168
+ )
169
+ # bypassing fetch_public_keys since all we need is the set of public_keys attribute on the client class
170
+ allow_any_instance_of(Descope::Api::V1::Auth).to receive(:fetch_public_keys).and_return(nil)
171
+ expect(
172
+ @instance.instance_variable_set(
173
+ :@public_keys, { 'some_kid' => [JWT::JWK.new(rsa_public), public_key['alg']] }
174
+ )
175
+ )
176
+
177
+ payload = { data: 'test' }
178
+ default_payload = { iss: CONTEXT[:issuer], sub: 'user123', aud: CONTEXT[:audience], exp: CLOCK + LEEWAY,
179
+ iat: CLOCK }
180
+ token = JWT.encode(default_payload.merge(payload), rsa_private, ALGORITHM)
181
+
182
+ expect do
183
+ sleep(20)
184
+ @instance.send(:validate_token, token)
185
+ end.to raise_error(
186
+ Descope::AuthException, /Signature has expired/
187
+ )
188
+
189
+ expect(
190
+ @instance.instance_variable_set(
191
+ :@jwt_validation_leeway, 120
192
+ )
193
+ )
194
+ expect do
195
+ @instance.send(:validate_token, token)
196
+ end.to_not raise_error
197
+ end
198
+
199
+ it 'is expected to use @public_keys in a thread safe manner' do
200
+ optional_parameters = { kid: 'some-kid', use: 'sig', alg: ALGORITHM }
201
+ jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
202
+ payload = { data: 'data' }
203
+ token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid])
204
+
205
+ # JSON Web Key Set for advertising your signing keys
206
+ jwks_hash = JWT::JWK::Set.new(jwk).export
207
+ jwks_hash.transform_keys!(&:to_s)['keys'][0].transform_keys!(&:to_s)
208
+
209
+ counter = 0
210
+ # stub the fetch_keys API call to get keys (/v2/keys/{project_id}) with the public key created above
211
+ allow(@instance).to receive(:token_validation_key) do |*args|
212
+ counter += 1
213
+ jwks_hash
214
+ end
215
+
216
+ # Create an array to hold threads
217
+ threads = []
218
+
219
+ # Make sure public_keys is only empty once
220
+
221
+ # Declare errors array with thread safety
222
+ errors = Concurrent::Array.new
223
+
224
+ 10.times do
225
+ threads << Thread.new do
226
+ begin
227
+ @instance.send(:validate_token, token)
228
+ rescue StandardError => e
229
+ puts "Error: #{e}"
230
+ errors << e
231
+ end
232
+ end
233
+ end
234
+
235
+ # Wait for all threads to finish
236
+ threads.each(&:join)
237
+
238
+ # Expect no errors
239
+ expect(errors).to have_attributes(length: 0)
240
+ expect(counter).to eq(1)
241
+ end
242
+ end
243
+
244
+ context '.select_tenant' do
245
+ it 'is expected to respond to select tenant' do
246
+ expect(@instance).to respond_to(:select_tenant)
247
+ end
248
+
249
+ it 'is expected to select tenant' do
250
+ jwt_response = { 'fake': 'response' }
251
+
252
+ expect(@instance).to receive(:post).with(
253
+ SELECT_TENANT_PATH, { tenantId: 'tenant123' }, {}, 'refresh-token'
254
+ ).and_return(jwt_response)
255
+
256
+ allow(@instance).to receive(:generate_jwt_response).and_return(jwt_response)
257
+
258
+ expect { @instance.select_tenant(tenant_id: 'tenant123', refresh_token: 'refresh-token') }.not_to raise_error
259
+ end
260
+ end
261
+
262
+ context '.validate_tenant_permissions' do
263
+ it 'is expected to respond to validate tenant permissions' do
264
+ expect(@instance).to respond_to(:validate_tenant_permissions)
265
+ end
266
+
267
+ it 'is expected to respond to validate permissions' do
268
+ expect(@instance).to respond_to(:validate_permissions)
269
+ end
270
+
271
+ # rubocop:disable Metrics/LineLength
272
+ it 'is expected to return false when jwt response are empty' do
273
+ expect(@instance.validate_permissions(jwt_response: {}, permissions: ['Perm 1'])).to be false
274
+ end
275
+
276
+ it 'is expected to return false when jwt response permissions are empty and the passed permissions are not empty' do
277
+ expect(@instance.validate_permissions(jwt_response: { 'permissions' => [] }, permissions: ['Perm 1'])).to be false
278
+ end
279
+
280
+ it 'is expected to return true when jwt response permissions are empty and the passed permissions are empty' do
281
+ expect(@instance.validate_permissions(jwt_response: { 'permissions' => [] }, permissions: [])).to be true
282
+ end
283
+
284
+ it 'is expected to return true when jwt response permissions and the passed permissions match' do
285
+ expect(@instance.validate_permissions(jwt_response: { 'permissions' => ['Perm 1'] }, permissions: 'Perm 1')).to be true
286
+ expect(@instance.validate_permissions(jwt_response: { 'permissions' => ['Perm 1'] }, permissions: ['Perm 1'])).to be true
287
+ end
288
+
289
+ it 'is expected to return false when jwt response permissions and the passed permissions do not match' do
290
+ expect(@instance.validate_permissions(jwt_response: { 'permissions' => ['Perm 1'] }, permissions: ['Perm 2'])).to be false
291
+ end
292
+
293
+ # # Tenant level
294
+ it 'is expected to return false when jwt response tenants are empty and the passed permissions are not empty' do
295
+ expect(@instance.validate_tenant_permissions(jwt_response: { 'tenants' => {} }, tenant: 't1', permissions: ['Perm 2'])).to be false
296
+ end
297
+
298
+ it 'is expected to return false when jwt response tenants has a tenant with empty or no permissions field and the passed permissions are not empty' do
299
+ expect(@instance.validate_tenant_permissions(jwt_response: { 'tenants' => { 't1' => {} } }, tenant: 't1', permissions: ['Perm 2'])).to be false
300
+ end
301
+
302
+ it 'is expected to return true when jwt response tenants has a tenant with permissions field and the passed permissions are empty' do
303
+ expect(@instance.validate_tenant_permissions(jwt_response: { 'tenants' => { 't1' => { 'permissions' => 'Perm 1' } } }, tenant: 't1', permissions: [])).to be true
304
+ end
305
+
306
+ it 'is expected to return true when jwt response tenants has a tenant with permissions field ad string and the passed permissions is an array' do
307
+ expect(@instance.validate_tenant_permissions(jwt_response: { 'tenants' => { 't1' => { 'permissions' => 'Perm 1' } } }, tenant: 't1', permissions: ['Perm 1'])).to be true
308
+ end
309
+
310
+ it 'is expected to return false when jwt response tenants has a tenant with permissions field and the passed permissions do not match' do
311
+ expect(@instance.validate_tenant_permissions(jwt_response: { 'tenants' => { 't1' => { 'permissions' => 'Perm 1' } } }, tenant: 't1', permissions: ['Perm 2'])).to be false
312
+ expect(@instance.validate_tenant_permissions(jwt_response: { 'tenants' => { 't1' => { 'permissions' => 'Perm 1' } } }, tenant: 't1', permissions: ['Perm 1', 'Perm 2'])).to be false
313
+ end
314
+
315
+ it 'is expected to return false when jwt response tenants and passed tenant do not match' do
316
+ expect(@instance.validate_tenant_permissions(jwt_response: { 'tenants' => { 't1' => { 'permissions' => 'Perm 1' } } }, tenant: 't2', permissions: [])).to be false
317
+ end
318
+ end
319
+
320
+ context '.validate_roles' do
321
+ it 'is expected to respond to validate roles' do
322
+ expect(@instance).to respond_to(:validate_roles)
323
+ end
324
+
325
+ it 'is expected to return false when jwt response are empty' do
326
+ expect(@instance.validate_roles(jwt_response: {}, roles: ['Role 1'])).to be false
327
+ end
328
+
329
+ it 'is expected to return false when jwt response roles are empty and the passed roles are not empty' do
330
+ expect(@instance.validate_roles(jwt_response: { 'roles' => [] }, roles: ['Role 1'])).to be false
331
+ end
332
+
333
+ it 'is expected to return true when jwt response roles are empty and the passed roles are empty' do
334
+ expect(@instance.validate_roles(jwt_response: { 'roles' => [] }, roles: [])).to be true
335
+ end
336
+
337
+ it 'is expected to return true when jwt response roles and the passed roles match' do
338
+ expect(@instance.validate_roles(jwt_response: { 'roles' => ['Role 1'] }, roles: 'Role 1')).to be true
339
+ expect(@instance.validate_roles(jwt_response: { 'roles' => ['Role 1'] }, roles: ['Role 1'])).to be true
340
+ end
341
+
342
+ it 'is expected to return false when jwt response roles and the passed roles do not match' do
343
+ expect(@instance.validate_roles(jwt_response: { 'roles' => ['Role 1'] }, roles: ['Role 2'])).to be false
344
+ end
345
+
346
+ # Tenant level
347
+ it 'is expected to return false when jwt response tenants are empty and the passed roles are not empty' do
348
+ expect(@instance.validate_tenant_roles(jwt_response: { 'tenants' => {} }, tenant: 't1', roles: ['Role 2'])).to be false
349
+ end
350
+
351
+ it 'is expected to return false when jwt response tenants has a tenant with empty or no roles field and the passed roles are not empty' do
352
+ expect(@instance.validate_tenant_roles(jwt_response: { 'tenants' => { 't1' => {} } }, tenant: 't1', roles: ['Role 2'])).to be false
353
+ end
354
+
355
+ it 'is expected to return true when jwt response tenants has a tenant with roles field and the passed roles are empty' do
356
+ expect(@instance.validate_tenant_roles(jwt_response: { 'tenants' => { 't1' => { 'roles' => 'Role 1' } } }, tenant: 't1', roles: [])).to be true
357
+ end
358
+
359
+ it 'is expected to return true when jwt response tenants has a tenant with roles field ad string and the passed roles is an array' do
360
+ expect(@instance.validate_tenant_roles(jwt_response: { 'tenants' => { 't1' => { 'roles' => 'Role 1' } } }, tenant: 't1', roles: ['Role 1'])).to be true
361
+ end
362
+
363
+ it 'is expected to return false when jwt response tenants has a tenant with roles field and the passed roles do not match' do
364
+ expect(@instance.validate_tenant_roles(jwt_response: { 'tenants' => { 't1' => { 'roles' => 'Role 1' } } }, tenant: 't1', roles: ['Role 2'])).to be false
365
+ expect(@instance.validate_tenant_roles(jwt_response: { 'tenants' => { 't1' => { 'roles' => 'Role 1' } } }, tenant: 't1', roles: ['Role 1', 'Role 2'])).to be false
366
+ end
367
+
368
+ it 'is expected to return false when jwt response tenants and passed tenant do not match' do
369
+ expect(@instance.validate_tenant_roles(jwt_response: { 'tenants' => { 't1' => { 'roles' => 'Role 1' } } }, tenant: 't2', roles: [])).to be false
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Descope::Api::V1::Management::AccessKey do
6
+ before(:all) do
7
+ dummy_instance = DummyClass.new
8
+ dummy_instance.extend(Descope::Api::V1::Management::AccessKey)
9
+ @instance = dummy_instance
10
+ end
11
+
12
+ context '.create' do
13
+ it 'should respond to .create_access_key' do
14
+ expect(@instance).to respond_to :create_access_key
15
+ end
16
+
17
+ it 'is expected to create access key' do
18
+ expect(@instance).to receive(:post).with(
19
+ ACCESS_KEY_CREATE_PATH, {
20
+ name: 'test',
21
+ expireTime: 0,
22
+ roleNames: ['test'],
23
+ keyTenants: [
24
+ { tenantId: 'test', roleNames: %w[test test2] }
25
+ ]
26
+ }
27
+ )
28
+ expect do
29
+ @instance.create_access_key(
30
+ name: 'test',
31
+ expire_time: 0,
32
+ role_names: ['test'],
33
+ key_tenants: [
34
+ { tenant_id: 'test', role_names: %w[test test2] }
35
+ ]
36
+ )
37
+ end.not_to raise_error
38
+ end
39
+ end
40
+
41
+ context '.load' do
42
+ it 'should respond to .load_access_key' do
43
+ expect(@instance).to respond_to :load_access_key
44
+ end
45
+
46
+ it 'is expected to load an access key' do
47
+ expect(@instance).to receive(:get).with(
48
+ ACCESS_KEY_LOAD_PATH, { id: '123' }
49
+ )
50
+ expect { @instance.load_access_key('123') }.not_to raise_error
51
+ end
52
+ end
53
+
54
+ context '.search_all_access_keys' do
55
+ it 'should respond to .search_all_access_keys_access_key' do
56
+ expect(@instance).to respond_to :search_all_access_keys
57
+ end
58
+
59
+ it 'is expected to search all access keys' do
60
+ expect(@instance).to receive(:post).with(
61
+ ACCESS_KEYS_SEARCH_PATH, { tenantIds: %w[123 456] }
62
+ )
63
+ expect { @instance.search_all_access_keys(%w[123 456]) }.not_to raise_error
64
+ end
65
+ end
66
+
67
+ context '.update_access_key' do
68
+ it 'should respond to .update_access_key' do
69
+ expect(@instance).to respond_to :update_access_key
70
+ end
71
+
72
+ it 'is expected to update an access keys' do
73
+ expect(@instance).to receive(:post).with(
74
+ ACCESS_KEY_UPDATE_PATH, { id: '123', name: 'test1' }
75
+ )
76
+ expect { @instance.update_access_key(id: '123', name: 'test1') }.not_to raise_error
77
+ end
78
+ end
79
+
80
+ context '.deactivate_access_key' do
81
+ it 'should respond to .deactivate_access_key' do
82
+ expect(@instance).to respond_to :deactivate_access_key
83
+ end
84
+
85
+ it 'is expected to deactivate an access keys' do
86
+ expect(@instance).to receive(:post).with(
87
+ ACCESS_KEY_DEACTIVATE_PATH, { id: '123' }
88
+ )
89
+ expect { @instance.deactivate_access_key('123') }.not_to raise_error
90
+ end
91
+ end
92
+
93
+ context '.activate_access_key' do
94
+ it 'should respond to .activate_access_key' do
95
+ expect(@instance).to respond_to :activate_access_key
96
+ end
97
+
98
+ it 'is expected to activate an access keys' do
99
+ expect(@instance).to receive(:post).with(
100
+ ACCESS_KEY_ACTIVATE_PATH, { id: '123' }
101
+ )
102
+ expect { @instance.activate_access_key('123') }.not_to raise_error
103
+ end
104
+ end
105
+
106
+ context '.delete_access_key' do
107
+ it 'should respond to .delete_access_key' do
108
+ expect(@instance).to respond_to :delete_access_key
109
+ end
110
+
111
+ it 'is expected to delete an access keys' do
112
+ expect(@instance).to receive(:post).with(
113
+ ACCESS_KEY_DELETE_PATH, { id: '123' }
114
+ )
115
+ expect { @instance.delete_access_key('123') }.not_to raise_error
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Descope::Api::V1::Management::Audit do
6
+ before(:all) do
7
+ dummy_instance = DummyClass.new
8
+ dummy_instance.extend(Descope::Api::V1::Management::Audit)
9
+ @instance = dummy_instance
10
+ end
11
+
12
+ context '.search' do
13
+ let(:mock_response) do
14
+ {
15
+ 'audits' => [
16
+ {
17
+ 'projectId' => 'abc',
18
+ 'userId' => 'abcde',
19
+ 'action' => 'get',
20
+ 'occurred' => 1,
21
+ 'method' => 'post',
22
+ 'device' => 'mobile',
23
+ 'geo' => 'US',
24
+ 'remoteAddress' => '1.2.3.4',
25
+ 'externalIds' => [],
26
+ 'tenants' => [],
27
+ 'data' => {}
28
+ }
29
+ ]
30
+ }
31
+ end
32
+
33
+ before do
34
+ allow(@instance).to receive(:post).twice.with(
35
+ AUDIT_SEARCH,
36
+ {
37
+ noTenants: true,
38
+ userIds: %w[user1 user2],
39
+ excludeActions: %w[exclude1 exclude2],
40
+ devices: %w[Bot Mobile Desktop Tablet Unknown],
41
+ methods: %w[otp totp magiclink oauth saml password],
42
+ geos: %w[US IL],
43
+ remoteAddresses: %w[remote1 remote2],
44
+ externalIds: %w[login1 login2],
45
+ tenants: %w[tenant1 tenant2],
46
+ text: 'text123',
47
+ from: 1_234_567_000,
48
+ to: 123_456_789_000
49
+ }
50
+ ).and_return(mock_response)
51
+ end
52
+
53
+ it 'should respond to .search' do
54
+ expect(@instance).to respond_to :audit_search
55
+ end
56
+
57
+ it 'is expected to search audit trail and get audits' do
58
+ res = nil # define res outside the block
59
+ expect do
60
+ res = @instance.audit_search(
61
+ no_tenants: true,
62
+ user_ids: %w[user1 user2],
63
+ exclude_actions: %w[exclude1 exclude2],
64
+ devices: %w[Bot Mobile Desktop Tablet Unknown],
65
+ methods: %w[otp totp magiclink oauth saml password],
66
+ geos: %w[US IL],
67
+ remote_addresses: %w[remote1 remote2],
68
+ login_ids: %w[login1 login2],
69
+ tenants: %w[tenant1 tenant2],
70
+ text: 'text123',
71
+ from_ts: 1_234_567,
72
+ to_ts: 123_456_789
73
+ )
74
+ end.not_to raise_error
75
+ expect(res['audits'][0]['projectId']).to eq('abc')
76
+ end
77
+ end
78
+ end