osso 0.0.5.pre.iota → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +4 -2
  3. data/.rubocop.yml +4 -1
  4. data/Gemfile.lock +41 -23
  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 +1 -0
  18. data/lib/osso/graphql/mutations.rb +1 -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/invite_admin_user.rb +43 -0
  22. data/lib/osso/graphql/query.rb +8 -0
  23. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +3 -3
  24. data/lib/osso/graphql/types.rb +2 -2
  25. data/lib/osso/graphql/types/admin_user.rb +9 -0
  26. data/lib/osso/graphql/types/base_object.rb +1 -1
  27. data/lib/osso/graphql/types/enterprise_account.rb +1 -0
  28. data/lib/osso/graphql/types/identity_provider.rb +2 -0
  29. data/lib/osso/graphql/types/identity_provider_service.rb +2 -1
  30. data/lib/osso/lib/route_map.rb +0 -16
  31. data/lib/osso/lib/saml_handler.rb +5 -0
  32. data/lib/osso/models/access_token.rb +4 -2
  33. data/lib/osso/models/account.rb +34 -0
  34. data/lib/osso/models/authorization_code.rb +2 -1
  35. data/lib/osso/models/enterprise_account.rb +3 -1
  36. data/lib/osso/models/identity_provider.rb +18 -4
  37. data/lib/osso/models/models.rb +1 -0
  38. data/lib/osso/models/oauth_client.rb +0 -1
  39. data/lib/osso/models/user.rb +2 -2
  40. data/lib/osso/routes/admin.rb +39 -33
  41. data/lib/osso/routes/auth.rb +9 -9
  42. data/lib/osso/routes/oauth.rb +35 -17
  43. data/lib/osso/version.rb +1 -1
  44. data/lib/osso/views/admin.erb +5 -0
  45. data/lib/osso/views/error.erb +1 -0
  46. data/lib/osso/views/layout.erb +0 -0
  47. data/lib/osso/views/multiple_providers.erb +1 -0
  48. data/lib/osso/views/welcome.erb +0 -0
  49. data/lib/tasks/bootstrap.rake +18 -4
  50. data/osso-rb.gemspec +5 -0
  51. data/spec/factories/account.rb +24 -0
  52. data/spec/factories/enterprise_account.rb +11 -3
  53. data/spec/factories/identity_providers.rb +10 -2
  54. data/spec/factories/user.rb +4 -0
  55. data/spec/graphql/mutations/configure_identity_provider_spec.rb +1 -1
  56. data/spec/graphql/mutations/create_enterprise_account_spec.rb +0 -14
  57. data/spec/graphql/mutations/create_identity_provider_spec.rb +59 -8
  58. data/spec/graphql/query/identity_provider_spec.rb +2 -2
  59. data/spec/models/enterprise_account_spec.rb +18 -0
  60. data/spec/models/identity_provider_spec.rb +24 -3
  61. data/spec/routes/admin_spec.rb +7 -41
  62. data/spec/routes/auth_spec.rb +17 -18
  63. data/spec/routes/oauth_spec.rb +88 -5
  64. data/spec/spec_helper.rb +3 -3
  65. data/spec/support/views/layout.erb +1 -0
  66. data/spec/support/views/multiple_providers.erb +1 -0
  67. metadata +91 -5
  68. data/spec/helpers/auth_spec.rb +0 -269
@@ -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,9 +8,9 @@ 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
@@ -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
@@ -27,15 +27,36 @@ describe Osso::Models::IdentityProvider do
27
27
  describe '#acs_url_validator' do
28
28
  it 'returns a regex escaped string' do
29
29
  allow(subject).to receive(:acs_url).and_return(
30
- 'https://foo.com/auth/saml/callback'
30
+ 'https://foo.com/auth/saml/callback',
31
31
  )
32
32
 
33
33
  expect(subject.acs_url_validator).to eq(
34
- 'https://foo\\.com/auth/saml/callback'
34
+ 'https://foo\\.com/auth/saml/callback',
35
35
  )
36
36
  end
37
37
  end
38
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
39
60
 
40
61
  describe '#saml_options' do
41
62
  it 'returns the required args' do
@@ -44,7 +65,7 @@ describe Osso::Models::IdentityProvider do
44
65
  domain: subject.domain,
45
66
  idp_cert: subject.sso_cert,
46
67
  idp_sso_target_url: subject.sso_url,
47
- issuer: subject.domain,
68
+ issuer: subject.sso_issuer,
48
69
  )
49
70
  end
50
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
@@ -3,15 +3,16 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Osso::Oauth do
6
+ before do
7
+ described_class.set(:views, spec_views)
8
+ end
9
+
6
10
  let(:client) { create(:oauth_client) }
7
11
 
8
12
  describe 'get /oauth/authorize' do
9
13
  describe 'with a valid client ID and redirect URI' do
10
14
  describe 'for a domain that does not belong to an enterprise' do
11
- # TODO: better error handling and test
12
15
  it 'renders an error page' do
13
- described_class.set(:views, spec_views)
14
-
15
16
  create(:enterprise_with_okta, domain: 'foo.com')
16
17
 
17
18
  get(
@@ -48,7 +49,7 @@ describe Osso::Oauth do
48
49
 
49
50
  describe 'for an enterprise domain with multiple SAML providers' do
50
51
  it 'renders the multiple providers screen' do
51
- enterprise = create(:enterprise_with_multiple_providers)
52
+ enterprise = create(:enterprise_with_multiple_providers, oauth_client: client)
52
53
 
53
54
  get(
54
55
  '/oauth/authorize',
@@ -59,6 +60,64 @@ describe Osso::Oauth do
59
60
  )
60
61
 
61
62
  expect(last_response).to be_ok
63
+ expect(last_response.body).to eq('MULITPLE PROVIDERS')
64
+ end
65
+ end
66
+
67
+ describe "for an existing user's email address" do
68
+ it 'redirects to /auth/saml/:provider_id' do
69
+ enterprise = create(:enterprise_with_okta, oauth_client: client)
70
+ provider_id = enterprise.identity_providers.first.id
71
+ user = create(:user, email: "user@#{enterprise.domain}", identity_provider_id: provider_id)
72
+
73
+ get(
74
+ '/oauth/authorize',
75
+ email: user.email,
76
+ client_id: client.identifier,
77
+ response_type: 'code',
78
+ redirect_uri: client.redirect_uri_values.sample,
79
+ )
80
+
81
+ expect(last_response).to be_redirect
82
+ follow_redirect!
83
+ expect(last_request.url).to match("auth/saml/#{provider_id}")
84
+ end
85
+ end
86
+
87
+ describe "for a new user's email address belonging to an enterprise with one SAML provider" do
88
+ it 'redirects to /auth/saml/:provider_id' do
89
+ enterprise = create(:enterprise_with_okta, oauth_client: client)
90
+
91
+ get(
92
+ '/oauth/authorize',
93
+ email: "user@#{enterprise.domain}",
94
+ client_id: client.identifier,
95
+ response_type: 'code',
96
+ redirect_uri: client.redirect_uri_values.sample,
97
+ )
98
+
99
+ provider_id = enterprise.identity_providers.first.id
100
+
101
+ expect(last_response).to be_redirect
102
+ follow_redirect!
103
+ expect(last_request.url).to match("auth/saml/#{provider_id}")
104
+ end
105
+ end
106
+
107
+ describe "for a new user's email address belonging to an enterprise with multiple SAML providers" do
108
+ it 'renders the multiple providers screen' do
109
+ enterprise = create(:enterprise_with_multiple_providers, oauth_client: client)
110
+
111
+ get(
112
+ '/oauth/authorize',
113
+ email: "user@#{enterprise.domain}",
114
+ client_id: client.identifier,
115
+ response_type: 'code',
116
+ redirect_uri: client.redirect_uri_values.sample,
117
+ )
118
+
119
+ expect(last_response).to be_ok
120
+ expect(last_response.body).to eq('MULITPLE PROVIDERS')
62
121
  end
63
122
  end
64
123
  end
@@ -90,7 +149,7 @@ describe Osso::Oauth do
90
149
  end
91
150
 
92
151
  describe 'get /oauth/me' do
93
- describe 'with a valid unexpired access token' do
152
+ describe 'with a valid unexpired access token in params' do
94
153
  it 'returns the user' do
95
154
  user = create(:user)
96
155
  code = user.authorization_codes.valid.first
@@ -105,6 +164,30 @@ describe Osso::Oauth do
105
164
  email: user.email,
106
165
  id: user.id,
107
166
  idp: 'Okta',
167
+ requested: code.requested.symbolize_keys,
168
+ )
169
+ end
170
+ end
171
+
172
+ describe 'with a valid unexpired access token in headers' do
173
+ it 'returns the user' do
174
+ user = create(:user)
175
+ code = user.authorization_codes.valid.first
176
+
177
+ get(
178
+ '/oauth/me',
179
+ nil,
180
+ {
181
+ 'HTTP_AUTHORIZATION' => "Bearer: #{code.access_token.to_bearer_token}",
182
+ },
183
+ )
184
+
185
+ expect(last_response.status).to eq(200)
186
+ expect(last_json_response).to eq(
187
+ email: user.email,
188
+ id: user.id,
189
+ idp: 'Okta',
190
+ requested: code.requested.symbolize_keys,
108
191
  )
109
192
  end
110
193
  end