osso 0.0.5.pre.zeta → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +4 -2
  3. data/.rubocop.yml +4 -1
  4. data/Gemfile.lock +48 -32
  5. data/LICENSE +21 -23
  6. data/bin/annotate +3 -1
  7. data/db/schema.rb +41 -3
  8. data/lib/osso/db/migrate/20200929154117_add_users_count_to_identity_providers_and_enterprise_accounts.rb +6 -0
  9. data/lib/osso/db/migrate/20201023142158_add_rodauth_tables.rb +47 -0
  10. data/lib/osso/db/migrate/20201105122026_add_token_index_to_access_tokens.rb +5 -0
  11. data/lib/osso/db/migrate/20201106154936_add_requested_to_authorization_codes_and_access_tokens.rb +6 -0
  12. data/lib/osso/db/migrate/20201109160851_add_sso_issuer_to_identity_providers.rb +12 -0
  13. data/lib/osso/db/migrate/20201110190754_remove_oauth_client_id_from_enterprise_accounts.rb +9 -0
  14. data/lib/osso/db/migrate/20201112160120_add_ping_to_identity_provider_service_enum.rb +28 -0
  15. data/lib/osso/error/account_configuration_error.rb +1 -0
  16. data/lib/osso/error/oauth_error.rb +6 -3
  17. data/lib/osso/graphql/mutation.rb +2 -0
  18. data/lib/osso/graphql/mutations.rb +2 -0
  19. data/lib/osso/graphql/mutations/create_enterprise_account.rb +0 -7
  20. data/lib/osso/graphql/mutations/create_identity_provider.rb +7 -6
  21. data/lib/osso/graphql/mutations/delete_identity_provider.rb +24 -0
  22. data/lib/osso/graphql/mutations/invite_admin_user.rb +43 -0
  23. data/lib/osso/graphql/query.rb +8 -0
  24. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +3 -3
  25. data/lib/osso/graphql/types.rb +2 -2
  26. data/lib/osso/graphql/types/admin_user.rb +9 -0
  27. data/lib/osso/graphql/types/base_object.rb +1 -1
  28. data/lib/osso/graphql/types/enterprise_account.rb +1 -0
  29. data/lib/osso/graphql/types/identity_provider.rb +3 -0
  30. data/lib/osso/graphql/types/identity_provider_service.rb +2 -1
  31. data/lib/osso/helpers/auth.rb +1 -1
  32. data/lib/osso/lib/route_map.rb +0 -15
  33. data/lib/osso/lib/saml_handler.rb +5 -0
  34. data/lib/osso/models/access_token.rb +4 -2
  35. data/lib/osso/models/account.rb +34 -0
  36. data/lib/osso/models/authorization_code.rb +2 -1
  37. data/lib/osso/models/enterprise_account.rb +3 -1
  38. data/lib/osso/models/identity_provider.rb +23 -5
  39. data/lib/osso/models/models.rb +1 -0
  40. data/lib/osso/models/oauth_client.rb +0 -1
  41. data/lib/osso/models/user.rb +2 -2
  42. data/lib/osso/routes/admin.rb +39 -33
  43. data/lib/osso/routes/auth.rb +9 -9
  44. data/lib/osso/routes/oauth.rb +35 -17
  45. data/lib/osso/version.rb +1 -1
  46. data/lib/osso/views/admin.erb +5 -0
  47. data/lib/osso/views/error.erb +1 -0
  48. data/lib/osso/views/layout.erb +0 -0
  49. data/lib/osso/views/multiple_providers.erb +1 -0
  50. data/lib/osso/views/welcome.erb +0 -0
  51. data/lib/tasks/bootstrap.rake +18 -4
  52. data/osso-rb.gemspec +5 -0
  53. data/spec/factories/account.rb +24 -0
  54. data/spec/factories/enterprise_account.rb +11 -3
  55. data/spec/factories/identity_providers.rb +10 -2
  56. data/spec/factories/user.rb +4 -0
  57. data/spec/graphql/mutations/configure_identity_provider_spec.rb +1 -1
  58. data/spec/graphql/mutations/create_enterprise_account_spec.rb +0 -14
  59. data/spec/graphql/mutations/create_identity_provider_spec.rb +59 -8
  60. data/spec/graphql/query/identity_provider_spec.rb +3 -2
  61. data/spec/models/enterprise_account_spec.rb +18 -0
  62. data/spec/models/identity_provider_spec.rb +35 -1
  63. data/spec/routes/admin_spec.rb +7 -41
  64. data/spec/routes/auth_spec.rb +17 -18
  65. data/spec/routes/oauth_spec.rb +88 -5
  66. data/spec/spec_helper.rb +3 -3
  67. data/spec/support/views/layout.erb +1 -0
  68. data/spec/support/views/multiple_providers.erb +1 -0
  69. metadata +92 -5
  70. data/spec/helpers/auth_spec.rb +0 -97
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ DB = Sequel.postgres(extensions: :activerecord_connection)
4
+
5
+ FactoryBot.define do
6
+ factory :account, class: Osso::Models::Account do
7
+ id { SecureRandom.uuid }
8
+ email { Faker::Internet.email }
9
+ end
10
+
11
+ factory :verified_account, parent: :account do
12
+ transient do
13
+ password { SecureRandom.urlsafe_base64(8) }
14
+ end
15
+ status_id { 2 }
16
+
17
+ after :create do |account|
18
+ DB[:account_password_hashes].insert(
19
+ id: account.id,
20
+ password_hash: BCrypt::Password.create('secret', cost: BCrypt::Engine::MIN_COST).to_s,
21
+ )
22
+ end
23
+ end
24
+ end
@@ -2,19 +2,22 @@
2
2
 
3
3
  FactoryBot.define do
4
4
  factory :enterprise_account, class: Osso::Models::EnterpriseAccount do
5
+ transient do
6
+ oauth_client { create(:oauth_client) }
7
+ end
5
8
  id { SecureRandom.uuid }
6
9
  name { Faker::Company.name }
7
10
  domain { Faker::Internet.domain_name }
8
- oauth_client
9
11
  end
10
12
 
11
13
  factory :enterprise_with_okta, parent: :enterprise_account do
12
- after :create do |enterprise|
14
+ after :create do |enterprise, evaluator|
13
15
  create(
14
16
  :configured_identity_provider,
15
17
  service: 'OKTA',
16
18
  domain: enterprise.domain,
17
19
  enterprise_account_id: enterprise.id,
20
+ oauth_client: evaluator.oauth_client,
18
21
  )
19
22
  end
20
23
  end
@@ -31,12 +34,16 @@ FactoryBot.define do
31
34
  end
32
35
 
33
36
  factory :enterprise_with_multiple_providers, parent: :enterprise_account do
34
- after :create do |enterprise|
37
+ transient do
38
+ oauth_client { nil }
39
+ end
40
+ after :create do |enterprise, evaluator|
35
41
  create(
36
42
  :configured_identity_provider,
37
43
  service: 'OKTA',
38
44
  domain: enterprise.domain,
39
45
  enterprise_account_id: enterprise.id,
46
+ oauth_client: evaluator.oauth_client,
40
47
  )
41
48
 
42
49
  create(
@@ -44,6 +51,7 @@ FactoryBot.define do
44
51
  service: 'AZURE',
45
52
  domain: enterprise.domain,
46
53
  enterprise_account_id: enterprise.id,
54
+ oauth_client: evaluator.oauth_client,
47
55
  )
48
56
  end
49
57
  end
@@ -21,6 +21,13 @@ FactoryBot.define do
21
21
  end
22
22
  end
23
23
 
24
+ factory :ping_identity_provider, parent: :identity_provider do
25
+ service { 'PING' }
26
+ sso_url do
27
+ 'https://auth.pingone.com/42cd503f-f0ba-47c7-a5b5-e69e9d8fab47/saml20/idp/sso'
28
+ end
29
+ end
30
+
24
31
  factory :configured_identity_provider, parent: :identity_provider do
25
32
  status { 'CONFIGURED' }
26
33
  sso_cert do
@@ -55,15 +62,16 @@ end
55
62
  # Table name: identity_providers
56
63
  #
57
64
  # id :uuid not null, primary key
58
- # service :string
65
+ # service :enum
59
66
  # domain :string not null
60
67
  # sso_url :string
61
68
  # sso_cert :text
62
69
  # enterprise_account_id :uuid
63
70
  # oauth_client_id :uuid
64
- # status :enum default("PENDING")
71
+ # status :enum default("pending")
65
72
  # created_at :datetime
66
73
  # updated_at :datetime
74
+ # users_count :integer default(0)
67
75
  #
68
76
  # Indexes
69
77
  #
@@ -12,6 +12,10 @@ FactoryBot.define do
12
12
  :authorization_code,
13
13
  user: user,
14
14
  redirect_uri: user.oauth_client.redirect_uri_values.sample,
15
+ requested: [
16
+ { domain: user.email.split('@')[1], email: nil },
17
+ { domain: nil, email: user.email },
18
+ ].sample,
15
19
  )
16
20
  end
17
21
  end
@@ -81,7 +81,7 @@ describe Osso::GraphQL::Schema do
81
81
  end
82
82
 
83
83
  it 'does not configure an identity provider' do
84
- expect(subject.dig('errors')).to_not be_empty
84
+ expect(subject['errors']).to_not be_empty
85
85
  end
86
86
  end
87
87
  end
@@ -47,7 +47,6 @@ describe Osso::GraphQL::Schema do
47
47
  input: {
48
48
  name: Faker::Company.name,
49
49
  domain: domain,
50
- oauthClientId: oauth_client.id,
51
50
  },
52
51
  }
53
52
  end
@@ -57,10 +56,6 @@ describe Osso::GraphQL::Schema do
57
56
  expect(subject.dig('data', 'createEnterpriseAccount', 'enterpriseAccount', 'domain')).
58
57
  to eq(domain)
59
58
  end
60
-
61
- it 'attaches the Enterprise Account to the correct OAuth Client' do
62
- expect { subject }.to change { oauth_client.enterprise_accounts.count }.by(1)
63
- end
64
59
  end
65
60
 
66
61
  describe 'for an internal scoped user' do
@@ -68,7 +63,6 @@ describe Osso::GraphQL::Schema do
68
63
  {
69
64
  scope: 'internal',
70
65
  email: 'user@saasco.com',
71
- oauth_client_id: oauth_client.identifier,
72
66
  }
73
67
  end
74
68
 
@@ -77,10 +71,6 @@ describe Osso::GraphQL::Schema do
77
71
  expect(subject.dig('data', 'createEnterpriseAccount', 'enterpriseAccount', 'domain')).
78
72
  to eq(domain)
79
73
  end
80
-
81
- it 'attaches the Enterprise Account to the correct OAuth Client' do
82
- expect { subject }.to change { oauth_client.enterprise_accounts.count }.by(1)
83
- end
84
74
  end
85
75
 
86
76
  describe 'for an email scoped user' do
@@ -97,10 +87,6 @@ describe Osso::GraphQL::Schema do
97
87
  expect(subject.dig('data', 'createEnterpriseAccount', 'enterpriseAccount', 'domain')).
98
88
  to eq(domain)
99
89
  end
100
-
101
- it 'attaches the Enterprise Account to the correct OAuth Client' do
102
- expect { subject }.to change { oauth_client.enterprise_accounts.count }.by(1)
103
- end
104
90
  end
105
91
  describe 'for the wrong email scoped user' do
106
92
  let(:current_context) do
@@ -5,6 +5,7 @@ require 'spec_helper'
5
5
  describe Osso::GraphQL::Schema do
6
6
  describe 'CreateIdentityProvider' do
7
7
  let(:enterprise_account) { create(:enterprise_account) }
8
+ let(:oauth_client) { create(:oauth_client) }
8
9
  let(:mutation) do
9
10
  <<~GRAPHQL
10
11
  mutation CreateIdentityProvider($input: CreateIdentityProviderInput!) {
@@ -34,7 +35,15 @@ describe Osso::GraphQL::Schema do
34
35
  { scope: 'admin' }
35
36
  end
36
37
  describe 'without a service' do
37
- let(:variables) { { input: { enterpriseAccountId: enterprise_account.id } } }
38
+ let(:variables) do
39
+ {
40
+ input:
41
+ {
42
+ enterpriseAccountId: enterprise_account.id,
43
+ oauthClientId: oauth_client.id,
44
+ },
45
+ }
46
+ end
38
47
 
39
48
  it 'creates an identity provider' do
40
49
  expect { subject }.to change { enterprise_account.identity_providers.count }.by(1)
@@ -44,8 +53,16 @@ describe Osso::GraphQL::Schema do
44
53
  end
45
54
 
46
55
  describe 'with a service' do
47
- let(:variables) { { input: { enterpriseAccountId: enterprise_account.id, service: 'OKTA' } } }
48
-
56
+ let(:variables) do
57
+ {
58
+ input:
59
+ {
60
+ enterpriseAccountId: enterprise_account.id,
61
+ service: 'OKTA',
62
+ oauthClientId: oauth_client.id,
63
+ },
64
+ }
65
+ end
49
66
  it 'creates an identity provider for given service ' do
50
67
  expect { subject }.to change { enterprise_account.identity_providers.count }.by(1)
51
68
  expect(subject.dig('data', 'createIdentityProvider', 'identityProvider', 'service')).
@@ -65,8 +82,16 @@ describe Osso::GraphQL::Schema do
65
82
  let(:enterprise_account) { create(:enterprise_account, domain: domain) }
66
83
 
67
84
  describe 'without a service' do
68
- let(:variables) { { input: { enterpriseAccountId: enterprise_account.id } } }
69
-
85
+ let(:variables) do
86
+ {
87
+ input:
88
+ {
89
+ enterpriseAccountId: enterprise_account.id,
90
+ oauthClientId: oauth_client.id,
91
+ },
92
+ }
93
+ end
94
+
70
95
  it 'creates an identity provider' do
71
96
  expect { subject }.to change { enterprise_account.identity_providers.count }.by(1)
72
97
  expect(subject.dig('data', 'createIdentityProvider', 'identityProvider', 'domain')).
@@ -75,7 +100,16 @@ describe Osso::GraphQL::Schema do
75
100
  end
76
101
 
77
102
  describe 'with a service' do
78
- let(:variables) { { input: { enterpriseAccountId: enterprise_account.id, service: 'OKTA' } } }
103
+ let(:variables) do
104
+ {
105
+ input:
106
+ {
107
+ enterpriseAccountId: enterprise_account.id,
108
+ oauthClientId: oauth_client.id,
109
+ service: 'OKTA',
110
+ },
111
+ }
112
+ end
79
113
 
80
114
  it 'creates an identity provider for given service ' do
81
115
  expect { subject }.to change { enterprise_account.identity_providers.count }.by(1)
@@ -97,7 +131,15 @@ describe Osso::GraphQL::Schema do
97
131
  let(:target_account) { create(:enterprise_account) }
98
132
 
99
133
  describe 'without a service' do
100
- let(:variables) { { input: { enterpriseAccountId: target_account.id, domain: domain } } }
134
+ let(:variables) do
135
+ {
136
+ input:
137
+ {
138
+ enterpriseAccountId: target_account.id,
139
+ oauthClientId: oauth_client.id,
140
+ },
141
+ }
142
+ end
101
143
 
102
144
  it 'does not creates a identity provider' do
103
145
  expect { subject }.to_not(change { Osso::Models::IdentityProvider.count })
@@ -105,7 +147,16 @@ describe Osso::GraphQL::Schema do
105
147
  end
106
148
 
107
149
  describe 'with a service' do
108
- let(:variables) { { input: { enterpriseAccountId: target_account.id, service: 'OKTA', domain: domain } } }
150
+ let(:variables) do
151
+ {
152
+ input:
153
+ {
154
+ enterpriseAccountId: target_account.id,
155
+ service: 'OKTA',
156
+ oauthClientId: oauth_client.id,
157
+ },
158
+ }
159
+ end
109
160
 
110
161
  it 'does not creates a identity provider' do
111
162
  expect { subject }.to_not(change { Osso::Models::IdentityProvider.count })
@@ -8,13 +8,14 @@ describe Osso::GraphQL::Schema do
8
8
  let(:domain) { Faker::Internet.domain_name }
9
9
  let(:variables) { { id: id } }
10
10
  let(:query) do
11
- <<~GRAPHQL
11
+ <<-GRAPHQL
12
12
  query IdentityProvider($id: ID!) {
13
- identityProvider(id: $id) {
13
+ identityProvider(id: $id) {
14
14
  id
15
15
  service
16
16
  domain
17
17
  acsUrl
18
+ acsUrlValidator
18
19
  ssoCert
19
20
  ssoUrl
20
21
  status
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Osso::Models::EnterpriseAccount do
6
+ describe 'validates_domain' do
7
+ it 'it returns false for an invalid domain' do
8
+ customer = described_class.new(
9
+ name: 'foo',
10
+ domain: ' foo.com',
11
+ )
12
+
13
+ customer.save
14
+
15
+ expect(customer.errors[:domain]).to include('is invalid')
16
+ end
17
+ end
18
+ end
@@ -24,6 +24,40 @@ describe Osso::Models::IdentityProvider do
24
24
  end
25
25
  end
26
26
 
27
+ describe '#acs_url_validator' do
28
+ it 'returns a regex escaped string' do
29
+ allow(subject).to receive(:acs_url).and_return(
30
+ 'https://foo.com/auth/saml/callback',
31
+ )
32
+
33
+ expect(subject.acs_url_validator).to eq(
34
+ 'https://foo\\.com/auth/saml/callback',
35
+ )
36
+ end
37
+ end
38
+
39
+ describe '#sso_issuer' do
40
+ it 'returns a url unique to self' do
41
+ ENV['HEROKU_APP_NAME'] = nil
42
+ ENV['BASE_URL'] = 'https://example.com'
43
+
44
+ expect(subject.sso_issuer).to eq(
45
+ "#{subject.domain}/#{subject.oauth_client_id}",
46
+ )
47
+ end
48
+
49
+ it 'returns a uri with protocol when required' do
50
+ ENV['HEROKU_APP_NAME'] = nil
51
+ ENV['BASE_URL'] = 'https://example.com'
52
+
53
+ idp = create(:ping_identity_provider)
54
+
55
+ expect(idp.sso_issuer).to eq(
56
+ "https://#{idp.domain}/#{idp.oauth_client_id}",
57
+ )
58
+ end
59
+ end
60
+
27
61
  describe '#saml_options' do
28
62
  it 'returns the required args' do
29
63
  expect(subject.saml_options).
@@ -31,7 +65,7 @@ describe Osso::Models::IdentityProvider do
31
65
  domain: subject.domain,
32
66
  idp_cert: subject.sso_cert,
33
67
  idp_sso_target_url: subject.sso_url,
34
- issuer: subject.domain,
68
+ issuer: subject.sso_issuer,
35
69
  )
36
70
  end
37
71
  end
@@ -3,56 +3,22 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Osso::Admin do
6
- let(:jwt_url) { 'https://foo.com/jwt' }
7
- let(:jwt_hmac_secret) { SecureRandom.hex(32) }
8
-
9
- before do
10
- ENV['JWT_URL'] = jwt_url
11
- ENV['JWT_HMAC_SECRET'] = jwt_hmac_secret
12
- described_class.set(:views, spec_views)
13
- end
14
-
15
6
  describe 'get /admin' do
16
- it 'redirects to JWT_URL without a session or token' do
7
+ it 'redirects to /login without a session' do
17
8
  get('/admin')
18
9
 
19
10
  expect(last_response).to be_redirect
20
11
  follow_redirect!
21
- expect(last_request.url).to eq(jwt_url)
22
- end
23
-
24
- it 'redirects to JWT_URL with an invalid token' do
25
- get('/admin', token: SecureRandom.hex(32))
26
-
27
- expect(last_response).to be_redirect
28
-
29
- follow_redirect!
30
-
31
- expect(last_request.url).to eq(jwt_url)
12
+ expect(last_request.url).to match('/login')
32
13
  end
33
14
 
34
- it 'chomps the token and redirects to request path with valid token' do
35
- token = JWT.encode(
36
- { email: 'admin@saas.com', scope: 'admin' },
37
- jwt_hmac_secret,
38
- 'HS256',
39
- )
40
-
41
- get('/admin', { admin_token: token })
42
-
43
- expect(last_response).to be_redirect
44
- follow_redirect!
45
- expect(last_request.url).to match('/admin')
46
- end
15
+ xit 'renders the admin page for a valid session token' do
16
+ password = SecureRandom.urlsafe_base64(16)
17
+ account = create(:verified_account, password: password)
47
18
 
48
- it 'renders the admin page for a valid session token' do
49
- token = JWT.encode(
50
- { email: 'admin@saas.com', scope: 'admin' },
51
- jwt_hmac_secret,
52
- 'HS256',
53
- )
19
+ post('/login', { email: account.email, password: password })
54
20
 
55
- get('/admin', {}, 'rack.session' => { admin_token: token })
21
+ get('/admin')
56
22
 
57
23
  expect(last_response).to be_ok
58
24
  end
@@ -182,29 +182,28 @@ describe Osso::Auth do
182
182
  it 'raises an error when email is missing' do
183
183
  mock_saml_omniauth(email: nil, id: SecureRandom.uuid)
184
184
 
185
-
186
- response = post(
187
- "/auth/saml/#{azure_provider.id}/callback",
188
- nil,
189
- {
190
- 'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
191
- },
192
- )
193
-
194
- expect(response.body).to eq('Osso::Error::MissingSamlEmailAttributeError')
195
- end
185
+ response = post(
186
+ "/auth/saml/#{azure_provider.id}/callback",
187
+ nil,
188
+ {
189
+ 'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
190
+ },
191
+ )
192
+
193
+ expect(response.body).to eq('Osso::Error::MissingSamlEmailAttributeError')
194
+ end
196
195
 
197
196
  it 'raises an error when id is missing' do
198
197
  mock_saml_omniauth(email: Faker::Internet.email, id: nil)
199
198
 
200
199
  response = post(
201
- "/auth/saml/#{azure_provider.id}/callback",
202
- nil,
203
- {
204
- 'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
205
- },
206
- )
207
-
200
+ "/auth/saml/#{azure_provider.id}/callback",
201
+ nil,
202
+ {
203
+ 'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
204
+ },
205
+ )
206
+
208
207
  expect(response.body).to eq('Osso::Error::MissingSamlIdAttributeError')
209
208
  end
210
209
  end