descope 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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