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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yaml +54 -0
- data/.gitignore +59 -0
- data/.release-please-manifest.json +3 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +10 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +90 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +204 -0
- data/LICENSE +21 -0
- data/README.md +1171 -0
- data/Rakefile +31 -0
- data/descope.gemspec +34 -0
- data/examples/ruby/Gemfile +4 -0
- data/examples/ruby/Gemfile.lock +41 -0
- data/examples/ruby/access_key_app.rb +45 -0
- data/examples/ruby/enchantedlink_app.rb +65 -0
- data/examples/ruby/magiclink_app.rb +81 -0
- data/examples/ruby/management/Gemfile +5 -0
- data/examples/ruby/management/Gemfile.lock +38 -0
- data/examples/ruby/management/access_key_app.rb +71 -0
- data/examples/ruby/management/audit_app.rb +25 -0
- data/examples/ruby/management/authz_app.rb +135 -0
- data/examples/ruby/management/authz_files.json +229 -0
- data/examples/ruby/management/flow_app.rb +57 -0
- data/examples/ruby/management/permission_app.rb +56 -0
- data/examples/ruby/management/role_app.rb +58 -0
- data/examples/ruby/management/tenant_app.rb +60 -0
- data/examples/ruby/management/user_app.rb +60 -0
- data/examples/ruby/oauth_app.rb +39 -0
- data/examples/ruby/otp_app.rb +50 -0
- data/examples/ruby/password_app.rb +76 -0
- data/examples/ruby/saml_app.rb +38 -0
- data/examples/ruby-on-rails-api/descope/.dockerignore +37 -0
- data/examples/ruby-on-rails-api/descope/.gitattributes +9 -0
- data/examples/ruby-on-rails-api/descope/.gitignore +40 -0
- data/examples/ruby-on-rails-api/descope/.node-version +1 -0
- data/examples/ruby-on-rails-api/descope/.ruby-version +1 -0
- data/examples/ruby-on-rails-api/descope/Dockerfile +75 -0
- data/examples/ruby-on-rails-api/descope/Gemfile +67 -0
- data/examples/ruby-on-rails-api/descope/Gemfile.lock +284 -0
- data/examples/ruby-on-rails-api/descope/Procfile.dev +3 -0
- data/examples/ruby-on-rails-api/descope/README.md +54 -0
- data/examples/ruby-on-rails-api/descope/Rakefile +6 -0
- data/examples/ruby-on-rails-api/descope/app/assets/builds/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/config/manifest.js +3 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/descope.jpeg +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/favicon.ico +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/logo192.png +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/logo512.png +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/stylesheets/application.bootstrap.scss +67 -0
- data/examples/ruby-on-rails-api/descope/app/channels/application_cable/channel.rb +4 -0
- data/examples/ruby-on-rails-api/descope/app/channels/application_cable/connection.rb +4 -0
- data/examples/ruby-on-rails-api/descope/app/controllers/application_controller.rb +2 -0
- data/examples/ruby-on-rails-api/descope/app/controllers/concerns/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/app/controllers/homepage_controller.rb +4 -0
- data/examples/ruby-on-rails-api/descope/app/controllers/session_controller.rb +66 -0
- data/examples/ruby-on-rails-api/descope/app/helpers/application_helper.rb +2 -0
- data/examples/ruby-on-rails-api/descope/app/helpers/homepage_helper.rb +2 -0
- data/examples/ruby-on-rails-api/descope/app/helpers/session_helper.rb +2 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/App.css +53 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/application.js +5 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/App.jsx +4 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/Dashboard.jsx +60 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/Home.jsx +27 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/Login.jsx +45 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/Profile.jsx +81 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/index.html +11 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/index.jsx +24 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/controllers/application.js +9 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/controllers/index.js +5 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/reportWebVitals.js +13 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/routes/index.jsx +17 -0
- data/examples/ruby-on-rails-api/descope/app/jobs/application_job.rb +7 -0
- data/examples/ruby-on-rails-api/descope/app/mailers/application_mailer.rb +4 -0
- data/examples/ruby-on-rails-api/descope/app/models/application_record.rb +3 -0
- data/examples/ruby-on-rails-api/descope/app/models/concerns/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/app/views/homepage/index.html.erb +2 -0
- data/examples/ruby-on-rails-api/descope/app/views/layouts/application.html.erb +16 -0
- data/examples/ruby-on-rails-api/descope/app/views/layouts/mailer.html.erb +13 -0
- data/examples/ruby-on-rails-api/descope/app/views/layouts/mailer.text.erb +1 -0
- data/examples/ruby-on-rails-api/descope/app/views/session/index.html.erb +2 -0
- data/examples/ruby-on-rails-api/descope/bin/bundle +109 -0
- data/examples/ruby-on-rails-api/descope/bin/dev +11 -0
- data/examples/ruby-on-rails-api/descope/bin/docker-entrypoint +8 -0
- data/examples/ruby-on-rails-api/descope/bin/rails +4 -0
- data/examples/ruby-on-rails-api/descope/bin/rake +4 -0
- data/examples/ruby-on-rails-api/descope/bin/setup +36 -0
- data/examples/ruby-on-rails-api/descope/build.js +30 -0
- data/examples/ruby-on-rails-api/descope/config/application.rb +42 -0
- data/examples/ruby-on-rails-api/descope/config/boot.rb +4 -0
- data/examples/ruby-on-rails-api/descope/config/cable.yml +10 -0
- data/examples/ruby-on-rails-api/descope/config/config.yml +9 -0
- data/examples/ruby-on-rails-api/descope/config/credentials.yml.enc +1 -0
- data/examples/ruby-on-rails-api/descope/config/database.yml +25 -0
- data/examples/ruby-on-rails-api/descope/config/environment.rb +5 -0
- data/examples/ruby-on-rails-api/descope/config/environments/development.rb +76 -0
- data/examples/ruby-on-rails-api/descope/config/environments/production.rb +97 -0
- data/examples/ruby-on-rails-api/descope/config/environments/test.rb +64 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/assets.rb +13 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/content_security_policy.rb +25 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/inflections.rb +16 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/load_config.rb +12 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/permissions_policy.rb +13 -0
- data/examples/ruby-on-rails-api/descope/config/locales/en.yml +31 -0
- data/examples/ruby-on-rails-api/descope/config/puma.rb +35 -0
- data/examples/ruby-on-rails-api/descope/config/routes.rb +18 -0
- data/examples/ruby-on-rails-api/descope/config/storage.yml +34 -0
- data/examples/ruby-on-rails-api/descope/config.ru +6 -0
- data/examples/ruby-on-rails-api/descope/db/seeds.rb +9 -0
- data/examples/ruby-on-rails-api/descope/lib/assets/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/lib/tasks/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/log/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/package-lock.json +19680 -0
- data/examples/ruby-on-rails-api/descope/package.json +51 -0
- data/examples/ruby-on-rails-api/descope/public/404.html +67 -0
- data/examples/ruby-on-rails-api/descope/public/422.html +67 -0
- data/examples/ruby-on-rails-api/descope/public/500.html +66 -0
- data/examples/ruby-on-rails-api/descope/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/ruby-on-rails-api/descope/public/apple-touch-icon.png +0 -0
- data/examples/ruby-on-rails-api/descope/public/favicon.ico +0 -0
- data/examples/ruby-on-rails-api/descope/public/robots.txt +1 -0
- data/examples/ruby-on-rails-api/descope/storage/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/tmp/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/tmp/pids/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/tmp/storage/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/vendor/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/yarn.lock +10780 -0
- data/lib/descope/api/v1/auth/enchantedlink.rb +156 -0
- data/lib/descope/api/v1/auth/magiclink.rb +170 -0
- data/lib/descope/api/v1/auth/oauth.rb +72 -0
- data/lib/descope/api/v1/auth/otp.rb +186 -0
- data/lib/descope/api/v1/auth/password.rb +100 -0
- data/lib/descope/api/v1/auth/saml.rb +48 -0
- data/lib/descope/api/v1/auth/totp.rb +72 -0
- data/lib/descope/api/v1/auth.rb +452 -0
- data/lib/descope/api/v1/management/access_key.rb +81 -0
- data/lib/descope/api/v1/management/audit.rb +82 -0
- data/lib/descope/api/v1/management/authz.rb +165 -0
- data/lib/descope/api/v1/management/common.rb +147 -0
- data/lib/descope/api/v1/management/flow.rb +55 -0
- data/lib/descope/api/v1/management/password.rb +58 -0
- data/lib/descope/api/v1/management/permission.rb +48 -0
- data/lib/descope/api/v1/management/project.rb +53 -0
- data/lib/descope/api/v1/management/role.rb +48 -0
- data/lib/descope/api/v1/management/scim.rb +206 -0
- data/lib/descope/api/v1/management/sso_settings.rb +153 -0
- data/lib/descope/api/v1/management/tenant.rb +71 -0
- data/lib/descope/api/v1/management/user.rb +619 -0
- data/lib/descope/api/v1/management.rb +38 -0
- data/lib/descope/api/v1/session.rb +84 -0
- data/lib/descope/api/v1.rb +13 -0
- data/lib/descope/client.rb +6 -0
- data/lib/descope/exception.rb +50 -0
- data/lib/descope/mixins/common.rb +129 -0
- data/lib/descope/mixins/headers.rb +15 -0
- data/lib/descope/mixins/http.rb +133 -0
- data/lib/descope/mixins/initializer.rb +80 -0
- data/lib/descope/mixins/logging.rb +30 -0
- data/lib/descope/mixins/validation.rb +79 -0
- data/lib/descope/mixins.rb +22 -0
- data/lib/descope/version.rb +7 -0
- data/lib/descope.rb +9 -0
- data/lib/descope_client.rb +5 -0
- data/release-please-config.json +18 -0
- data/renovate.json +6 -0
- data/spec/factories/user.rb +16 -0
- data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +159 -0
- data/spec/lib.descope/api/v1/auth/magiclink_spec.rb +282 -0
- data/spec/lib.descope/api/v1/auth/oauth_spec.rb +117 -0
- data/spec/lib.descope/api/v1/auth/otp_spec.rb +285 -0
- data/spec/lib.descope/api/v1/auth/password_spec.rb +124 -0
- data/spec/lib.descope/api/v1/auth/saml_spec.rb +55 -0
- data/spec/lib.descope/api/v1/auth/totp_spec.rb +70 -0
- data/spec/lib.descope/api/v1/auth_spec.rb +372 -0
- data/spec/lib.descope/api/v1/management/access_key_spec.rb +118 -0
- data/spec/lib.descope/api/v1/management/audit_spec.rb +78 -0
- data/spec/lib.descope/api/v1/management/authz_spec.rb +336 -0
- data/spec/lib.descope/api/v1/management/flow_spec.rb +78 -0
- data/spec/lib.descope/api/v1/management/password_spec.rb +25 -0
- data/spec/lib.descope/api/v1/management/permission_spec.rb +81 -0
- data/spec/lib.descope/api/v1/management/project_spec.rb +63 -0
- data/spec/lib.descope/api/v1/management/role_spec.rb +85 -0
- data/spec/lib.descope/api/v1/management/scim_spec.rb +312 -0
- data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +172 -0
- data/spec/lib.descope/api/v1/management/tenant_spec.rb +141 -0
- data/spec/lib.descope/api/v1/management/user_spec.rb +667 -0
- data/spec/lib.descope/api/v1/session_spec.rb +117 -0
- data/spec/lib.descope/client_spec.rb +40 -0
- data/spec/spec_helper.rb +72 -0
- data/spec/support/client_config.rb +14 -0
- data/spec/support/dummy_class.rb +36 -0
- data/spec/support/utils.rb +32 -0
- 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
|