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