osso 0.0.5.pre.zeta → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +6 -4
  3. data/.github/dependabot.yml +8 -0
  4. data/.github/workflows/automerge.yml +19 -0
  5. data/.rubocop.yml +4 -1
  6. data/Gemfile +2 -2
  7. data/Gemfile.lock +69 -51
  8. data/LICENSE +21 -23
  9. data/Rakefile +2 -0
  10. data/bin/annotate +3 -1
  11. data/db/schema.rb +41 -3
  12. data/lib/osso.rb +0 -1
  13. data/lib/osso/db/migrate/20200929154117_add_users_count_to_identity_providers_and_enterprise_accounts.rb +6 -0
  14. data/lib/osso/db/migrate/20201023142158_add_rodauth_tables.rb +47 -0
  15. data/lib/osso/db/migrate/20201105122026_add_token_index_to_access_tokens.rb +5 -0
  16. data/lib/osso/db/migrate/20201106154936_add_requested_to_authorization_codes_and_access_tokens.rb +6 -0
  17. data/lib/osso/db/migrate/20201109160851_add_sso_issuer_to_identity_providers.rb +12 -0
  18. data/lib/osso/db/migrate/20201110190754_remove_oauth_client_id_from_enterprise_accounts.rb +9 -0
  19. data/lib/osso/db/migrate/20201112160120_add_ping_to_identity_provider_service_enum.rb +28 -0
  20. data/lib/osso/db/migrate/20201125143501_add_salesforce_to_provider_service_enum.rb +28 -0
  21. data/lib/osso/error/account_configuration_error.rb +1 -0
  22. data/lib/osso/error/oauth_error.rb +6 -3
  23. data/lib/osso/graphql/mutation.rb +2 -0
  24. data/lib/osso/graphql/mutations.rb +2 -0
  25. data/lib/osso/graphql/mutations/create_enterprise_account.rb +0 -7
  26. data/lib/osso/graphql/mutations/create_identity_provider.rb +7 -6
  27. data/lib/osso/graphql/mutations/delete_identity_provider.rb +24 -0
  28. data/lib/osso/graphql/mutations/invite_admin_user.rb +43 -0
  29. data/lib/osso/graphql/query.rb +8 -0
  30. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +3 -3
  31. data/lib/osso/graphql/types.rb +2 -2
  32. data/lib/osso/graphql/types/admin_user.rb +9 -0
  33. data/lib/osso/graphql/types/base_object.rb +1 -1
  34. data/lib/osso/graphql/types/enterprise_account.rb +1 -0
  35. data/lib/osso/graphql/types/identity_provider.rb +3 -0
  36. data/lib/osso/graphql/types/identity_provider_service.rb +3 -1
  37. data/lib/osso/lib/app_config.rb +1 -1
  38. data/lib/osso/lib/route_map.rb +0 -15
  39. data/lib/osso/lib/saml_handler.rb +5 -0
  40. data/lib/osso/models/access_token.rb +4 -2
  41. data/lib/osso/models/account.rb +34 -0
  42. data/lib/osso/models/authorization_code.rb +2 -1
  43. data/lib/osso/models/enterprise_account.rb +3 -1
  44. data/lib/osso/models/identity_provider.rb +24 -5
  45. data/lib/osso/models/models.rb +1 -0
  46. data/lib/osso/models/oauth_client.rb +0 -1
  47. data/lib/osso/models/user.rb +2 -2
  48. data/lib/osso/routes/admin.rb +39 -33
  49. data/lib/osso/routes/auth.rb +9 -9
  50. data/lib/osso/routes/oauth.rb +42 -18
  51. data/lib/osso/version.rb +1 -1
  52. data/lib/osso/views/admin.erb +5 -0
  53. data/lib/osso/views/error.erb +1 -0
  54. data/lib/osso/views/layout.erb +0 -0
  55. data/lib/osso/views/multiple_providers.erb +1 -0
  56. data/lib/osso/views/welcome.erb +0 -0
  57. data/lib/tasks/bootstrap.rake +25 -4
  58. data/osso-rb.gemspec +5 -0
  59. data/spec/factories/account.rb +24 -0
  60. data/spec/factories/enterprise_account.rb +11 -3
  61. data/spec/factories/identity_providers.rb +10 -2
  62. data/spec/factories/user.rb +4 -0
  63. data/spec/graphql/mutations/configure_identity_provider_spec.rb +1 -1
  64. data/spec/graphql/mutations/create_enterprise_account_spec.rb +0 -14
  65. data/spec/graphql/mutations/create_identity_provider_spec.rb +59 -8
  66. data/spec/graphql/query/identity_provider_spec.rb +3 -2
  67. data/spec/models/enterprise_account_spec.rb +18 -0
  68. data/spec/models/identity_provider_spec.rb +36 -1
  69. data/spec/routes/admin_spec.rb +7 -41
  70. data/spec/routes/auth_spec.rb +17 -18
  71. data/spec/routes/oauth_spec.rb +102 -5
  72. data/spec/spec_helper.rb +3 -3
  73. data/spec/support/views/hosted_login.erb +1 -0
  74. data/spec/support/views/layout.erb +1 -0
  75. data/spec/support/views/multiple_providers.erb +1 -0
  76. metadata +108 -7
  77. data/lib/osso/helpers/auth.rb +0 -94
  78. data/lib/osso/helpers/helpers.rb +0 -8
  79. data/spec/helpers/auth_spec.rb +0 -97
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Osso
4
- VERSION = '0.0.5-zeta'
4
+ VERSION = '0.0.8'
5
5
  end
@@ -0,0 +1,5 @@
1
+ <%#
2
+ NB: this file exists so that the admin routes have something to render in spec.
3
+ In real-world usage, those routes render an index.html file that includes the
4
+ React app.
5
+ %>
@@ -0,0 +1 @@
1
+ <%= @error %>
File without changes
@@ -0,0 +1 @@
1
+ MULITPLE PROVIDERS
File without changes
@@ -7,12 +7,33 @@ require 'osso'
7
7
  namespace :osso do
8
8
  desc 'Bootstrap Osso data for a deployment'
9
9
  task :bootstrap do
10
- %w[Production Staging Development].each do |environement|
10
+ %w[Production Staging Development].each do |environment|
11
11
  Osso::Models::OauthClient.create!(
12
- name: environement,
13
- )
12
+ name: environment,
13
+ ) unless Osso::Models::OauthClient.find_by_name(environment)
14
14
  end
15
15
 
16
- Osso::Models::AppConfig.create!
16
+ Osso::Models::AppConfig.create
17
+
18
+ admin_email = ENV['ADMIN_EMAIL']
19
+
20
+ if admin_email
21
+ admin = Osso::Models::Account.create(
22
+ email: admin_email,
23
+ status_id: 1,
24
+ role: 'admin',
25
+ )
26
+
27
+ base_uri = URI.parse(ENV['BASE_URL'])
28
+
29
+ rodauth = Osso::Admin.rodauth.new(Osso::Admin.new({
30
+ 'HTTP_HOST' => base_uri.host,
31
+ 'SERVER_NAME' => base_uri.to_s,
32
+ 'rack.url_scheme' => base_uri.scheme
33
+ }))
34
+
35
+ account = rodauth.account_from_login(admin_email)
36
+ rodauth.setup_account_verification
37
+ end
17
38
  end
18
39
  end
@@ -16,14 +16,19 @@ Gem::Specification.new do |spec|
16
16
  spec.license = 'MIT'
17
17
 
18
18
  spec.add_runtime_dependency 'activesupport', '>= 6.0.3.2'
19
+ spec.add_runtime_dependency 'bcrypt', '~> 3.1.13'
19
20
  spec.add_runtime_dependency 'graphql'
20
21
  spec.add_runtime_dependency 'jwt'
22
+ spec.add_runtime_dependency 'mail', '~> 2.7.1'
21
23
  spec.add_runtime_dependency 'omniauth-multi-provider'
22
24
  spec.add_runtime_dependency 'omniauth-saml'
23
25
  spec.add_runtime_dependency 'rack', '>= 2.1.4'
24
26
  spec.add_runtime_dependency 'rack-contrib'
25
27
  spec.add_runtime_dependency 'rack-oauth2'
26
28
  spec.add_runtime_dependency 'rake'
29
+ spec.add_runtime_dependency 'rodauth', '~> 2.6.0'
30
+ spec.add_runtime_dependency 'sequel', '>= 5.37', '< 5.40'
31
+ spec.add_runtime_dependency 'sequel-activerecord_connection', '>= 0.3', '< 2.0'
27
32
  spec.add_runtime_dependency 'sinatra'
28
33
  spec.add_runtime_dependency 'sinatra-activerecord'
29
34
  spec.add_runtime_dependency 'sinatra-contrib'
@@ -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,8 @@ 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,
69
+ name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
35
70
  )
36
71
  end
37
72
  end