osso 0.0.6.alpha → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +3 -2
  3. data/.github/dependabot.yml +8 -0
  4. data/.github/workflows/automerge.yml +19 -0
  5. data/Gemfile +3 -3
  6. data/Gemfile.lock +62 -56
  7. data/Rakefile +3 -0
  8. data/bin/console +3 -0
  9. data/db/schema.rb +2 -2
  10. data/lib/osso.rb +1 -1
  11. data/lib/osso/db/migrate/20201125143501_add_salesforce_to_provider_service_enum.rb +28 -0
  12. data/lib/osso/graphql/mutations/configure_identity_provider.rb +4 -1
  13. data/lib/osso/graphql/mutations/create_enterprise_account.rb +4 -1
  14. data/lib/osso/graphql/mutations/create_identity_provider.rb +6 -1
  15. data/lib/osso/graphql/mutations/create_oauth_client.rb +4 -1
  16. data/lib/osso/graphql/mutations/delete_enterprise_account.rb +5 -1
  17. data/lib/osso/graphql/mutations/delete_identity_provider.rb +4 -1
  18. data/lib/osso/graphql/mutations/delete_oauth_client.rb +4 -1
  19. data/lib/osso/graphql/mutations/invite_admin_user.rb +6 -0
  20. data/lib/osso/graphql/mutations/regenerate_oauth_credentials.rb +4 -1
  21. data/lib/osso/graphql/mutations/set_redirect_uris.rb +2 -0
  22. data/lib/osso/graphql/mutations/update_app_config.rb +4 -1
  23. data/lib/osso/graphql/types/identity_provider_service.rb +1 -0
  24. data/lib/osso/lib/analytics.rb +55 -0
  25. data/lib/osso/lib/app_config.rb +1 -1
  26. data/lib/osso/models/identity_provider.rb +1 -0
  27. data/lib/osso/routes/admin.rb +39 -7
  28. data/lib/osso/routes/oauth.rb +10 -4
  29. data/lib/osso/version.rb +1 -1
  30. data/osso-rb.gemspec +4 -3
  31. data/spec/models/identity_provider_spec.rb +1 -0
  32. data/spec/routes/admin_spec.rb +27 -9
  33. data/spec/routes/oauth_spec.rb +14 -0
  34. data/spec/support/views/hosted_login.erb +1 -0
  35. metadata +51 -17
  36. data/lib/osso/helpers/auth.rb +0 -94
  37. data/lib/osso/helpers/helpers.rb +0 -8
  38. data/spec/routes/app_spec.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e336bd3d2b66ba5530df382560eafc3800e4141d8f0f2ad3c09ddfcff4e89c5b
4
- data.tar.gz: 833e3c5c353823e8591c836ec53a03405ad0870025fac4103350b525b32f5254
3
+ metadata.gz: f3def53429479fcc6f8174b8065830233e8437b3e6cd5e6a2647caa710622028
4
+ data.tar.gz: dbdaa671b3a6b2ca07e5d48ed247eefbdbe6bd06423247975821088aea097f45
5
5
  SHA512:
6
- metadata.gz: 0745075df908207f91971faba07ade0a64d61347a96c07386b1ba97a1a2a6d63173184e1a59b859e256a4e43045f535f19fc27b6b7e9c67ebf7cfcd616da602d
7
- data.tar.gz: ecb5eb7a7195f3f40563ec22972a697ad08d9ad259d69e37dc90fb59717d623c2b825d2776970911c8aa6c0284502d441bc145fde8542674c23ed3657b4fc4f9
6
+ metadata.gz: '0391fb57427e5f417ee19f2566cda5e432834bff640d14516abdf54a716c401c6ed42559ed5f366855936e2f6d976f86e1c64c1bfd497f9b3443f8a54f240485'
7
+ data.tar.gz: 1bdc64d6943502b18f7801003131d379879bad62265bc80f4f5cd5c9547604d7fb60b35a174ce9875a1d5b13ef673535bed4566d14d5888de1552b40a9c0d26b
@@ -12,6 +12,7 @@ steps:
12
12
  - coverage/*
13
13
 
14
14
  - name: ":codeclimate:"
15
+ soft_fail: true
15
16
  plugins:
16
17
  - jobready/codeclimate-test-reporter#v2.0:
17
18
  artifact: "coverage/.resultset.json"
@@ -19,8 +20,8 @@ steps:
19
20
  prefix: '/var/lib/buildkite-agent/builds/enterprise-oss-bk-1/enterpriseoss/osso-rb/'
20
21
 
21
22
  - block: ":rubygems: Publish :red_button:"
22
- branches: "main"
23
+ if: build.tag != null
23
24
 
24
25
  - name: "Push :rubygems:"
25
26
  commands: "./bin/publish"
26
- branches: "main"
27
+ if: build.tag != null
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ labels:
8
+ - "dependencies"
@@ -0,0 +1,19 @@
1
+ name: auto-merge
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ jobs:
7
+ auto-approve:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v2
11
+ - uses: ahmadnassri/action-dependabot-auto-merge@v2
12
+ with:
13
+ target: minor
14
+ github-token: ${{ secrets.TOKEN }}
15
+ - uses: hmarr/auto-approve-action@v2.0.0
16
+ if: github.actor == 'dependabot[bot]'
17
+ with:
18
+ github-token: "${{ secrets.TOKEN }}"
19
+
data/Gemfile CHANGED
@@ -10,10 +10,10 @@ group :test do
10
10
  gem 'faker'
11
11
  gem 'pg'
12
12
  gem 'rack-test'
13
- gem 'rspec', '~> 3.2'
13
+ gem 'rspec', '~> 3.10'
14
14
  gem 'rubocop'
15
- gem 'simplecov', '= 0.17', require: false
16
- gem 'webmock', '~> 3.0'
15
+ gem 'simplecov', '0.21.1', require: false
16
+ gem 'webmock', '~> 3.11'
17
17
  end
18
18
 
19
19
  gemspec
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- osso (0.0.6.alpha)
4
+ osso (0.0.11)
5
5
  activesupport (>= 6.0.3.2)
6
6
  bcrypt (~> 3.1.13)
7
7
  graphql
@@ -9,13 +9,14 @@ PATH
9
9
  mail (~> 2.7.1)
10
10
  omniauth-multi-provider
11
11
  omniauth-saml
12
+ posthog-ruby
12
13
  rack (>= 2.1.4)
13
14
  rack-contrib
14
15
  rack-oauth2
15
16
  rake
16
- rodauth (~> 2.6.0)
17
- sequel (~> 5.37.0)
18
- sequel-activerecord_connection (~> 0.3)
17
+ rodauth (>= 2.6, < 2.8)
18
+ sequel (>= 5.37, < 5.41)
19
+ sequel-activerecord_connection (>= 0.3, < 2.0)
19
20
  sinatra
20
21
  sinatra-activerecord
21
22
  sinatra-contrib
@@ -23,20 +24,22 @@ PATH
23
24
  GEM
24
25
  remote: https://rubygems.org/
25
26
  specs:
26
- activemodel (6.0.3.4)
27
- activesupport (= 6.0.3.4)
28
- activerecord (6.0.3.4)
29
- activemodel (= 6.0.3.4)
30
- activesupport (= 6.0.3.4)
31
- activesupport (6.0.3.4)
27
+ activemodel (6.1.0)
28
+ activesupport (= 6.1.0)
29
+ activerecord (6.1.0)
30
+ activemodel (= 6.1.0)
31
+ activesupport (= 6.1.0)
32
+ activesupport (6.1.0)
32
33
  concurrent-ruby (~> 1.0, >= 1.0.2)
33
- i18n (>= 0.7, < 2)
34
- minitest (~> 5.1)
35
- tzinfo (~> 1.1)
36
- zeitwerk (~> 2.2, >= 2.2.2)
34
+ i18n (>= 1.6, < 2)
35
+ minitest (>= 5.1)
36
+ tzinfo (~> 2.0)
37
+ zeitwerk (~> 2.3)
37
38
  addressable (2.7.0)
38
39
  public_suffix (>= 2.0.2, < 5.0)
39
40
  aes_key_wrap (1.1.0)
41
+ after_commit_everywhere (0.1.5)
42
+ activerecord (>= 4.2)
40
43
  annotate (3.1.1)
41
44
  activerecord (>= 3.2, < 7.0)
42
45
  rake (>= 10.4, < 14.0)
@@ -52,10 +55,10 @@ GEM
52
55
  activerecord
53
56
  database_cleaner (~> 1.8.0)
54
57
  diff-lcs (1.4.4)
55
- docile (1.3.2)
58
+ docile (1.3.4)
56
59
  factory_bot (6.1.0)
57
60
  activesupport (>= 5.0.0)
58
- faker (2.14.0)
61
+ faker (2.15.1)
59
62
  i18n (>= 1.6, < 2)
60
63
  graphql (1.11.6)
61
64
  hashdiff (1.0.1)
@@ -63,7 +66,6 @@ GEM
63
66
  httpclient (2.8.3)
64
67
  i18n (1.8.5)
65
68
  concurrent-ruby (~> 1.0)
66
- json (2.3.1)
67
69
  json-jwt (1.13.0)
68
70
  activesupport (>= 4.2)
69
71
  aes_key_wrap
@@ -73,13 +75,14 @@ GEM
73
75
  mini_mime (>= 0.1.1)
74
76
  method_source (1.0.0)
75
77
  mini_mime (1.0.2)
76
- mini_portile2 (2.4.0)
78
+ mini_portile2 (2.5.0)
77
79
  minitest (5.14.2)
78
80
  multi_json (1.15.0)
79
81
  mustermann (1.1.1)
80
82
  ruby2_keywords (~> 0.0.1)
81
- nokogiri (1.10.10)
82
- mini_portile2 (~> 2.4.0)
83
+ nokogiri (1.11.1)
84
+ mini_portile2 (~> 2.5.0)
85
+ racc (~> 1.4)
83
86
  omniauth (1.9.1)
84
87
  hashie (>= 3.4.6)
85
88
  rack (>= 1.6.2, < 3)
@@ -88,16 +91,18 @@ GEM
88
91
  omniauth-saml (1.10.3)
89
92
  omniauth (~> 1.3, >= 1.3.2)
90
93
  ruby-saml (~> 1.9)
91
- parallel (1.19.2)
92
- parser (2.7.2.0)
94
+ parallel (1.20.1)
95
+ parser (3.0.0.0)
93
96
  ast (~> 2.4.1)
94
97
  pg (1.2.3)
98
+ posthog-ruby (1.1.0)
95
99
  pry (0.13.1)
96
100
  coderay (~> 1.1)
97
101
  method_source (~> 1.0)
98
102
  public_suffix (4.0.6)
103
+ racc (1.5.2)
99
104
  rack (2.2.3)
100
- rack-contrib (2.2.0)
105
+ rack-contrib (2.3.0)
101
106
  rack (~> 2.0)
102
107
  rack-oauth2 (1.16.0)
103
108
  activesupport
@@ -110,51 +115,53 @@ GEM
110
115
  rack-test (1.1.0)
111
116
  rack (>= 1.0, < 3)
112
117
  rainbow (3.0.0)
113
- rake (13.0.1)
114
- regexp_parser (1.8.2)
118
+ rake (13.0.3)
119
+ regexp_parser (2.0.2)
115
120
  rexml (3.2.4)
116
- roda (3.38.0)
121
+ roda (3.39.0)
117
122
  rack
118
- rodauth (2.6.0)
123
+ rodauth (2.7.0)
119
124
  roda (>= 2.6.0)
120
125
  sequel (>= 4)
121
- rspec (3.9.0)
122
- rspec-core (~> 3.9.0)
123
- rspec-expectations (~> 3.9.0)
124
- rspec-mocks (~> 3.9.0)
125
- rspec-core (3.9.3)
126
- rspec-support (~> 3.9.3)
127
- rspec-expectations (3.9.3)
126
+ rspec (3.10.0)
127
+ rspec-core (~> 3.10.0)
128
+ rspec-expectations (~> 3.10.0)
129
+ rspec-mocks (~> 3.10.0)
130
+ rspec-core (3.10.0)
131
+ rspec-support (~> 3.10.0)
132
+ rspec-expectations (3.10.0)
128
133
  diff-lcs (>= 1.2.0, < 2.0)
129
- rspec-support (~> 3.9.0)
130
- rspec-mocks (3.9.1)
134
+ rspec-support (~> 3.10.0)
135
+ rspec-mocks (3.10.0)
131
136
  diff-lcs (>= 1.2.0, < 2.0)
132
- rspec-support (~> 3.9.0)
133
- rspec-support (3.9.4)
134
- rubocop (1.1.0)
137
+ rspec-support (~> 3.10.0)
138
+ rspec-support (3.10.0)
139
+ rubocop (1.7.0)
135
140
  parallel (~> 1.10)
136
141
  parser (>= 2.7.1.5)
137
142
  rainbow (>= 2.2.2, < 4.0)
138
- regexp_parser (>= 1.8)
143
+ regexp_parser (>= 1.8, < 3.0)
139
144
  rexml
140
- rubocop-ast (>= 1.0.1)
145
+ rubocop-ast (>= 1.2.0, < 2.0)
141
146
  ruby-progressbar (~> 1.7)
142
147
  unicode-display_width (>= 1.4.0, < 2.0)
143
- rubocop-ast (1.1.0)
148
+ rubocop-ast (1.3.0)
144
149
  parser (>= 2.7.1.5)
145
150
  ruby-progressbar (1.10.1)
146
151
  ruby-saml (1.11.0)
147
152
  nokogiri (>= 1.5.10)
148
153
  ruby2_keywords (0.0.2)
149
- sequel (5.37.0)
150
- sequel-activerecord_connection (0.4.1)
154
+ sequel (5.39.0)
155
+ sequel-activerecord_connection (1.2.0)
151
156
  activerecord (>= 4.2, < 7)
157
+ after_commit_everywhere (~> 0.1.5)
152
158
  sequel (~> 5.16)
153
- simplecov (0.17.0)
159
+ simplecov (0.21.1)
154
160
  docile (~> 1.1)
155
- json (>= 1.8, < 3)
156
- simplecov-html (~> 0.10.0)
157
- simplecov-html (0.10.2)
161
+ simplecov-html (~> 0.11)
162
+ simplecov_json_formatter (~> 0.1)
163
+ simplecov-html (0.12.3)
164
+ simplecov_json_formatter (0.1.2)
158
165
  sinatra (2.1.0)
159
166
  mustermann (~> 1.0)
160
167
  rack (~> 2.2)
@@ -169,16 +176,15 @@ GEM
169
176
  rack-protection (= 2.1.0)
170
177
  sinatra (= 2.1.0)
171
178
  tilt (~> 2.0)
172
- thread_safe (0.3.6)
173
179
  tilt (2.0.10)
174
- tzinfo (1.2.7)
175
- thread_safe (~> 0.1)
180
+ tzinfo (2.0.3)
181
+ concurrent-ruby (~> 1.0)
176
182
  unicode-display_width (1.7.0)
177
- webmock (3.9.3)
183
+ webmock (3.11.0)
178
184
  addressable (>= 2.3.6)
179
185
  crack (>= 0.3.2)
180
186
  hashdiff (>= 0.4.0, < 2.0.0)
181
- zeitwerk (2.4.1)
187
+ zeitwerk (2.4.2)
182
188
 
183
189
  PLATFORMS
184
190
  ruby
@@ -193,10 +199,10 @@ DEPENDENCIES
193
199
  pg
194
200
  pry
195
201
  rack-test
196
- rspec (~> 3.2)
202
+ rspec (~> 3.10)
197
203
  rubocop
198
- simplecov (= 0.17)
199
- webmock (~> 3.0)
204
+ simplecov (= 0.21.1)
205
+ webmock (~> 3.11)
200
206
 
201
207
  BUNDLED WITH
202
208
  2.1.4
data/Rakefile CHANGED
@@ -4,6 +4,9 @@
4
4
  # to tell ActiveRecord where to find the database
5
5
  # schema and migrations
6
6
 
7
+ ENV['SESSION_SECRET'] ||= 'rake-secret'
8
+ ENV['BASE_URL'] ||= 'https://example.com'
9
+
7
10
  require 'bundler/gem_tasks'
8
11
  require 'sinatra/activerecord/rake'
9
12
  require './lib/osso'
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ ENV['SESSION_SECRET'] ||= 'irb-secret'
5
+ ENV['BASE_URL'] ||= 'https://example.com'
6
+
4
7
  require 'bundler/setup'
5
8
  require 'osso'
6
9
 
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 2020_11_12_160120) do
13
+ ActiveRecord::Schema.define(version: 2020_11_25_143501) do
14
14
 
15
15
  # These are extensions that must be enabled in order to support this database
16
16
  enable_extension "citext"
@@ -57,7 +57,7 @@ ActiveRecord::Schema.define(version: 2020_11_12_160120) do
57
57
  t.citext "email", null: false
58
58
  t.integer "status_id", default: 1, null: false
59
59
  t.string "role", default: "admin", null: false
60
- t.uuid "oauth_client_id"
60
+ t.string "oauth_client_id"
61
61
  t.index ["email"], name: "index_accounts_on_email", unique: true, where: "(status_id = ANY (ARRAY[1, 2]))"
62
62
  t.index ["oauth_client_id"], name: "index_accounts_on_oauth_client_id"
63
63
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Osso
4
4
  require_relative 'osso/error/error'
5
- require_relative 'osso/helpers/helpers'
5
+ require_relative 'osso/lib/analytics'
6
6
  require_relative 'osso/lib/app_config'
7
7
  require_relative 'osso/lib/oauth2_token'
8
8
  require_relative 'osso/lib/route_map'
@@ -0,0 +1,28 @@
1
+ class AddSalesforceToProviderServiceEnum < ActiveRecord::Migration[6.0]
2
+ disable_ddl_transaction!
3
+
4
+ def up
5
+ execute <<-SQL
6
+ ALTER TYPE identity_provider_service ADD VALUE 'SALESFORCE';
7
+ SQL
8
+ end
9
+
10
+ def down
11
+ execute <<~SQL
12
+ CREATE TYPE identity_provider_service_new AS ENUM ('AZURE', 'OKTA', 'ONELOGIN', 'GOOGLE', 'PING');
13
+
14
+ -- Remove values that won't be compatible with new definition
15
+ DELETE FROM identity_providers WHERE service = 'SALESFORCE';
16
+
17
+ -- Convert to new type, casting via text representation
18
+ ALTER TABLE identity_providers
19
+ ALTER COLUMN service TYPE identity_provider_service_new
20
+ USING (service::text::identity_provider_service_new);
21
+
22
+ -- and swap the types
23
+ DROP TYPE identity_provider_service;
24
+
25
+ ALTER TYPE identity_provider_service_new RENAME TO identity_provider_service;
26
+ SQL
27
+ end
28
+ end
@@ -15,7 +15,10 @@ module Osso
15
15
  def resolve(**args)
16
16
  provider = identity_provider(**args)
17
17
 
18
- return response_data(identity_provider: provider) if provider.update(args)
18
+ if provider.update(args)
19
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: args)
20
+ return response_data(identity_provider: provider)
21
+ end
19
22
 
20
23
  response_error(provider.errors)
21
24
  end
@@ -15,7 +15,10 @@ module Osso
15
15
  def resolve(**args)
16
16
  enterprise_account = Osso::Models::EnterpriseAccount.new(args)
17
17
 
18
- return response_data(enterprise_account: enterprise_account) if enterprise_account.save
18
+ if enterprise_account.save
19
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: args)
20
+ return response_data(enterprise_account: enterprise_account)
21
+ end
19
22
 
20
23
  response_error(enterprise_account.errors)
21
24
  end
@@ -22,7 +22,12 @@ module Osso
22
22
  oauth_client_id: oauth_client_id,
23
23
  )
24
24
 
25
- return response_data(identity_provider: identity_provider) if identity_provider.save
25
+ if identity_provider.save
26
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: {
27
+ service: service, enterprise_account_id: enterprise_account_id, oauth_client_id: oauth_client_id
28
+ })
29
+ return response_data(identity_provider: identity_provider)
30
+ end
26
31
 
27
32
  response_error(identity_provider.errors)
28
33
  end
@@ -14,7 +14,10 @@ module Osso
14
14
  def resolve(**args)
15
15
  oauth_client = Osso::Models::OauthClient.new(args)
16
16
 
17
- return response_data(oauth_client: oauth_client) if oauth_client.save
17
+ if oauth_client.save
18
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: args)
19
+ return response_data(oauth_client: oauth_client)
20
+ end
18
21
 
19
22
  response_error(oauth_client.errors)
20
23
  end
@@ -18,7 +18,11 @@ module Osso
18
18
  def resolve(**args)
19
19
  customer = enterprise_account(**args)
20
20
 
21
- return response_data(enterprise_account: nil) if customer.destroy
21
+ if customer.destroy
22
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: args)
23
+ return response_data(enterprise_account: nil)
24
+ end
25
+
22
26
 
23
27
  response_error(customer.errors)
24
28
  end
@@ -14,7 +14,10 @@ module Osso
14
14
  def resolve(id:)
15
15
  identity_provider = Osso::Models::IdentityProvider.find(id)
16
16
 
17
- return response_data(identity_provider: nil) if identity_provider.destroy
17
+ if identity_provider.destroy
18
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: { id: id })
19
+ return response_data(identity_provider: nil)
20
+ end
18
21
 
19
22
  response_error(identity_provider.errors)
20
23
  end
@@ -14,7 +14,10 @@ module Osso
14
14
  def resolve(id:)
15
15
  oauth_client = Osso::Models::OauthClient.find(id)
16
16
 
17
- return response_data(oauth_client: nil) if oauth_client.destroy
17
+ if oauth_client.destroy
18
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: { id: id })
19
+ return response_data(oauth_client: nil)
20
+ end
18
21
 
19
22
  response_error(oauth_client.errors)
20
23
  end
@@ -23,6 +23,12 @@ module Osso
23
23
  if admin_user.save
24
24
  verify_user(email)
25
25
 
26
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: {
27
+ invited_email: email,
28
+ invited_role: role,
29
+ invited_oauth_client_id: oauth_client_id,
30
+ })
31
+
26
32
  return response_data(admin_user: admin_user)
27
33
  end
28
34
 
@@ -15,7 +15,10 @@ module Osso
15
15
  oauth_client = Osso::Models::OauthClient.find(id)
16
16
  oauth_client.regenerate_secrets!
17
17
 
18
- return response_data(oauth_client: oauth_client) if oauth_client.save
18
+ if oauth_client.save
19
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: { oauth_client_id: id })
20
+ return response_data(oauth_client: oauth_client)
21
+ end
19
22
 
20
23
  response_error(oauth_client.errors)
21
24
  end
@@ -18,6 +18,8 @@ module Osso
18
18
  update_existing(oauth_client, redirect_uris)
19
19
  create_new(oauth_client, redirect_uris)
20
20
 
21
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: redirect_uris)
22
+
21
23
  response_data(oauth_client: oauth_client.reload)
22
24
  rescue StandardError => e
23
25
  response_error(e)
@@ -15,7 +15,10 @@ module Osso
15
15
 
16
16
  def resolve(**args)
17
17
  app_config = Osso::Models::AppConfig.find
18
- return response_data(app_config: app_config) if app_config.update(**args)
18
+ if app_config.update(**args)
19
+ Osso::Analytics.capture(email: context[:email], event: self.class.name.demodulize, properties: args)
20
+ return response_data(app_config: app_config)
21
+ end
19
22
 
20
23
  response_error(app_config.errors)
21
24
  end
@@ -9,6 +9,7 @@ module Osso
9
9
  value('OKTA', 'Okta Identity Provider', value: 'OKTA')
10
10
  value('ONELOGIN', 'OneLogin Identity Provider', value: 'ONELOGIN')
11
11
  value('PING', 'PingID Identity Provider', value: 'PING')
12
+ value('SALESFORCE', 'Salesforce Identity Provider', value: 'SALESFORCE')
12
13
  end
13
14
  end
14
15
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'posthog-ruby'
4
+
5
+ module Osso
6
+ # Osso::Analytics provides an interface to track product analytics for any provider.
7
+ # Osso recommends PostHog as an open source solution for your product analytics needs.
8
+ # If you want to use another product analytics provider, you can patch the Osso::Analytics
9
+ # class yourself in your parent application. Be sure to implement the public
10
+ # .identify and .capture class methods with the required method signatures and require
11
+ # your class after requiring Osso.
12
+ class Analytics
13
+ class << self
14
+ def identify(email:, properties: {})
15
+ return unless configured?
16
+
17
+ client.identify({
18
+ distinct_id: email,
19
+ properties: properties.merge(instance_properties),
20
+ })
21
+ end
22
+
23
+ def capture(email:, event:, properties: {})
24
+ return unless configured?
25
+
26
+ client.capture(
27
+ distinct_id: email,
28
+ event: event,
29
+ properties: properties.merge(instance_properties),
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def configured?
36
+ ENV['POSTHOG_API_KEY'].present?
37
+ end
38
+
39
+ def client
40
+ @client ||= PostHog::Client.new({
41
+ api_key: ENV['POSTHOG_API_KEY'],
42
+ api_host: ENV['POSTHOG_HOST'],
43
+ on_error: Proc.new { |status, msg| print msg }
44
+ })
45
+ end
46
+
47
+ def instance_properties
48
+ {
49
+ instance_url: ENV['BASE_URL'],
50
+ osso_plan: ENV['OSSO_PLAN'],
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -7,7 +7,7 @@ module Osso
7
7
  def self.included(klass)
8
8
  klass.class_eval do
9
9
  use Rack::JSONBodyParser
10
- use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
10
+ use Rack::Session::Cookie, secret: ENV.fetch('SESSION_SECRET')
11
11
 
12
12
  error ActiveRecord::RecordNotFound do
13
13
  status 404
@@ -30,6 +30,7 @@ module Osso
30
30
  idp_sso_target_url: sso_url,
31
31
  idp_cert: sso_cert,
32
32
  issuer: sso_issuer,
33
+ name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
33
34
  }
34
35
  end
35
36
 
@@ -8,18 +8,47 @@ DEFAULT_VIEWS_DIR = File.join(File.expand_path(Bundler.root), 'views/rodauth')
8
8
  module Osso
9
9
  class Admin < Roda
10
10
  DB = Sequel.postgres(extensions: :activerecord_connection)
11
- use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
12
-
11
+ use Rack::Session::Cookie, secret: ENV.fetch('SESSION_SECRET')
12
+
13
+ plugin :json
13
14
  plugin :middleware
14
15
  plugin :render, engine: 'erb', views: ENV['RODAUTH_VIEWS'] || DEFAULT_VIEWS_DIR
15
16
  plugin :route_csrf
16
17
 
17
18
  plugin :rodauth do
18
- enable :login, :verify_account
19
+ enable :login, :verify_account, :jwt
20
+
21
+ base_uri = URI.parse(ENV.fetch('BASE_URL'))
22
+ base_url base_uri
23
+ domain base_uri.host
24
+
25
+ jwt_secret ENV.fetch('SESSION_SECRET')
26
+ only_json? false
27
+
28
+ email_from { "Osso <no-reply@#{domain}>" }
19
29
  verify_account_set_password? true
20
- already_logged_in { redirect login_redirect }
21
30
  use_database_authentication_functions? false
22
31
 
32
+ after_login do
33
+ Osso::Analytics.identify(email: account[:email], properties: account)
34
+ end
35
+
36
+ verify_account_view do
37
+ render :admin
38
+ end
39
+
40
+ login_view do
41
+ render :admin
42
+ end
43
+
44
+ verify_account_email_subject do
45
+ DB[:accounts].one? ? 'Your Osso instance is ready' : 'You\'ve been invited to start using Osso'
46
+ end
47
+
48
+ verify_account_email_body do
49
+ DB[:accounts].one? ? render('verify-first-account-email') : render('verify-account-email')
50
+ end
51
+
23
52
  before_create_account_route do
24
53
  request.halt unless DB[:accounts].empty?
25
54
  end
@@ -31,13 +60,16 @@ module Osso
31
60
  r.rodauth
32
61
 
33
62
  def current_account
34
- Osso::Models::Account.find(rodauth.session['account_id']).
35
- context.
63
+ Osso::Models::Account.find(
64
+ rodauth.
65
+ session.
66
+ to_hash.
67
+ stringify_keys['account_id']
68
+ ).context.
36
69
  merge({ rodauth: rodauth })
37
70
  end
38
71
 
39
72
  r.on 'admin' do
40
- rodauth.require_authentication
41
73
  erb :admin, layout: false
42
74
  end
43
75
 
@@ -16,13 +16,14 @@ module Osso
16
16
  # Once they complete IdP login, they will be returned to the
17
17
  # redirect_uri with an authorization code parameter.
18
18
  get '/authorize' do
19
- identity_providers = find_providers
20
-
21
19
  validate_oauth_request(env)
22
20
 
23
- redirect "/auth/saml/#{identity_providers.first.id}" if identity_providers.one?
21
+ return erb :hosted_login if render_hosted_login?
22
+
23
+ @providers = find_providers
24
+
25
+ redirect "/auth/saml/#{@providers.first.id}" if @providers.one?
24
26
 
25
- @providers = identity_providers.not_pending
26
27
  return erb :multiple_providers if @providers.count > 1
27
28
 
28
29
  raise Osso::Error::MissingConfiguredIdentityProvider.new(domain: params[:domain])
@@ -61,6 +62,10 @@ module Osso
61
62
 
62
63
  private
63
64
 
65
+ def render_hosted_login?
66
+ [params[:email], params[:domain]].all?(&:nil?)
67
+ end
68
+
64
69
  def find_providers
65
70
  if params[:email]
66
71
  user = Osso::Models::User.
@@ -71,6 +76,7 @@ module Osso
71
76
 
72
77
  Osso::Models::IdentityProvider.
73
78
  joins(:oauth_client).
79
+ not_pending.
74
80
  where(
75
81
  domain: domain_from_params,
76
82
  oauth_clients: { identifier: params[:client_id] },
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Osso
4
- VERSION = '0.0.6.alpha'
4
+ VERSION = '0.0.11'
5
5
  end
@@ -22,13 +22,14 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency 'mail', '~> 2.7.1'
23
23
  spec.add_runtime_dependency 'omniauth-multi-provider'
24
24
  spec.add_runtime_dependency 'omniauth-saml'
25
+ spec.add_runtime_dependency 'posthog-ruby'
25
26
  spec.add_runtime_dependency 'rack', '>= 2.1.4'
26
27
  spec.add_runtime_dependency 'rack-contrib'
27
28
  spec.add_runtime_dependency 'rack-oauth2'
28
29
  spec.add_runtime_dependency 'rake'
29
- spec.add_runtime_dependency 'rodauth', '~> 2.6.0'
30
- spec.add_runtime_dependency 'sequel', '~> 5.37.0'
31
- spec.add_runtime_dependency 'sequel-activerecord_connection', '~> 0.3'
30
+ spec.add_runtime_dependency 'rodauth', '>= 2.6', '< 2.8'
31
+ spec.add_runtime_dependency 'sequel', '>= 5.37', '< 5.41'
32
+ spec.add_runtime_dependency 'sequel-activerecord_connection', '>= 0.3', '< 2.0'
32
33
  spec.add_runtime_dependency 'sinatra'
33
34
  spec.add_runtime_dependency 'sinatra-activerecord'
34
35
  spec.add_runtime_dependency 'sinatra-contrib'
@@ -66,6 +66,7 @@ describe Osso::Models::IdentityProvider do
66
66
  idp_cert: subject.sso_cert,
67
67
  idp_sso_target_url: subject.sso_url,
68
68
  issuer: subject.sso_issuer,
69
+ name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
69
70
  )
70
71
  end
71
72
  end
@@ -4,23 +4,41 @@ require 'spec_helper'
4
4
 
5
5
  describe Osso::Admin do
6
6
  describe 'get /admin' do
7
- it 'redirects to /login without a session' do
7
+ it 'renders the admin layout' do
8
8
  get('/admin')
9
9
 
10
- expect(last_response).to be_redirect
11
- follow_redirect!
12
- expect(last_request.url).to match('/login')
10
+ expect(last_response).to be_ok
13
11
  end
12
+ end
14
13
 
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)
14
+ describe 'post /graphql' do
15
+ let(:account) { create(:account) }
18
16
 
19
- post('/login', { email: account.email, password: password })
17
+ it 'runs a GraphQL query with a valid jwt' do
18
+ allow_any_instance_of(described_class.rodauth).to receive(:logged_in?).and_return(true)
19
+ allow(Osso::Models::Account).to receive(:find).and_return(account)
20
+ allow(Osso::GraphQL::Schema).to receive(:execute).and_return({graphql: true})
20
21
 
21
- get('/admin')
22
+ header 'Content-Type', 'application/json'
23
+ post("/graphql")
22
24
 
23
25
  expect(last_response).to be_ok
26
+ expect(last_json_response).to eq({graphql: true})
27
+ end
28
+
29
+ it 'returns a 400 for an invalid jwt' do
30
+ header 'Content-Type', 'application/json'
31
+ header 'Authorization', 'Bearer bad-token'
32
+ post("/graphql")
33
+
34
+ expect(last_response.status).to eq 400
35
+ end
36
+
37
+ it 'returns a 401 without a jwt' do
38
+ header 'Content-Type', 'application/json'
39
+ post("/graphql")
40
+
41
+ expect(last_response.status).to eq 401
24
42
  end
25
43
  end
26
44
  end
@@ -27,6 +27,20 @@ describe Osso::Oauth do
27
27
  end
28
28
  end
29
29
 
30
+ describe 'for a request without email or domain' do
31
+ it 'redirects to /auth/saml/:provider_id' do
32
+ get(
33
+ '/oauth/authorize',
34
+ client_id: client.identifier,
35
+ response_type: 'code',
36
+ redirect_uri: client.redirect_uri_values.sample,
37
+ )
38
+
39
+ expect(last_response).to be_ok
40
+ expect(last_response.body).to eq('HOSTED LOGIN')
41
+ end
42
+ end
43
+
30
44
  describe 'for an enterprise domain with one SAML provider' do
31
45
  it 'redirects to /auth/saml/:provider_id' do
32
46
  enterprise = create(:enterprise_with_okta, oauth_client: client)
@@ -0,0 +1 @@
1
+ HOSTED LOGIN
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6.alpha
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Bauch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-24 00:00:00.000000000 Z
11
+ date: 2021-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: posthog-ruby
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rack
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -168,44 +182,62 @@ dependencies:
168
182
  name: rodauth
169
183
  requirement: !ruby/object:Gem::Requirement
170
184
  requirements:
171
- - - "~>"
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '2.6'
188
+ - - "<"
172
189
  - !ruby/object:Gem::Version
173
- version: 2.6.0
190
+ version: '2.8'
174
191
  type: :runtime
175
192
  prerelease: false
176
193
  version_requirements: !ruby/object:Gem::Requirement
177
194
  requirements:
178
- - - "~>"
195
+ - - ">="
179
196
  - !ruby/object:Gem::Version
180
- version: 2.6.0
197
+ version: '2.6'
198
+ - - "<"
199
+ - !ruby/object:Gem::Version
200
+ version: '2.8'
181
201
  - !ruby/object:Gem::Dependency
182
202
  name: sequel
183
203
  requirement: !ruby/object:Gem::Requirement
184
204
  requirements:
185
- - - "~>"
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '5.37'
208
+ - - "<"
186
209
  - !ruby/object:Gem::Version
187
- version: 5.37.0
210
+ version: '5.41'
188
211
  type: :runtime
189
212
  prerelease: false
190
213
  version_requirements: !ruby/object:Gem::Requirement
191
214
  requirements:
192
- - - "~>"
215
+ - - ">="
193
216
  - !ruby/object:Gem::Version
194
- version: 5.37.0
217
+ version: '5.37'
218
+ - - "<"
219
+ - !ruby/object:Gem::Version
220
+ version: '5.41'
195
221
  - !ruby/object:Gem::Dependency
196
222
  name: sequel-activerecord_connection
197
223
  requirement: !ruby/object:Gem::Requirement
198
224
  requirements:
199
- - - "~>"
225
+ - - ">="
200
226
  - !ruby/object:Gem::Version
201
227
  version: '0.3'
228
+ - - "<"
229
+ - !ruby/object:Gem::Version
230
+ version: '2.0'
202
231
  type: :runtime
203
232
  prerelease: false
204
233
  version_requirements: !ruby/object:Gem::Requirement
205
234
  requirements:
206
- - - "~>"
235
+ - - ">="
207
236
  - !ruby/object:Gem::Version
208
237
  version: '0.3'
238
+ - - "<"
239
+ - !ruby/object:Gem::Version
240
+ version: '2.0'
209
241
  - !ruby/object:Gem::Dependency
210
242
  name: sinatra
211
243
  requirement: !ruby/object:Gem::Requirement
@@ -305,6 +337,8 @@ files:
305
337
  - ".buildkite/hooks/pre-command"
306
338
  - ".buildkite/pipeline.yml"
307
339
  - ".buildkite/template.yml"
340
+ - ".github/dependabot.yml"
341
+ - ".github/workflows/automerge.yml"
308
342
  - ".gitignore"
309
343
  - ".rspec"
310
344
  - ".rubocop.yml"
@@ -350,6 +384,7 @@ files:
350
384
  - lib/osso/db/migrate/20201109160851_add_sso_issuer_to_identity_providers.rb
351
385
  - lib/osso/db/migrate/20201110190754_remove_oauth_client_id_from_enterprise_accounts.rb
352
386
  - lib/osso/db/migrate/20201112160120_add_ping_to_identity_provider_service_enum.rb
387
+ - lib/osso/db/migrate/20201125143501_add_salesforce_to_provider_service_enum.rb
353
388
  - lib/osso/error/account_configuration_error.rb
354
389
  - lib/osso/error/error.rb
355
390
  - lib/osso/error/missing_saml_attribute_error.rb
@@ -392,8 +427,7 @@ files:
392
427
  - lib/osso/graphql/types/oauth_client.rb
393
428
  - lib/osso/graphql/types/redirect_uri.rb
394
429
  - lib/osso/graphql/types/redirect_uri_input.rb
395
- - lib/osso/helpers/auth.rb
396
- - lib/osso/helpers/helpers.rb
430
+ - lib/osso/lib/analytics.rb
397
431
  - lib/osso/lib/app_config.rb
398
432
  - lib/osso/lib/oauth2_token.rb
399
433
  - lib/osso/lib/route_map.rb
@@ -442,7 +476,6 @@ files:
442
476
  - spec/models/enterprise_account_spec.rb
443
477
  - spec/models/identity_provider_spec.rb
444
478
  - spec/routes/admin_spec.rb
445
- - spec/routes/app_spec.rb
446
479
  - spec/routes/auth_spec.rb
447
480
  - spec/routes/oauth_spec.rb
448
481
  - spec/spec_helper.rb
@@ -450,6 +483,7 @@ files:
450
483
  - spec/support/spec_app.rb
451
484
  - spec/support/views/admin.erb
452
485
  - spec/support/views/error.erb
486
+ - spec/support/views/hosted_login.erb
453
487
  - spec/support/views/layout.erb
454
488
  - spec/support/views/multiple_providers.erb
455
489
  homepage: https://github.com/enterprise-oss/osso-rb
@@ -467,9 +501,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
467
501
  version: 2.3.0
468
502
  required_rubygems_version: !ruby/object:Gem::Requirement
469
503
  requirements:
470
- - - ">"
504
+ - - ">="
471
505
  - !ruby/object:Gem::Version
472
- version: 1.3.1
506
+ version: '0'
473
507
  requirements: []
474
508
  rubygems_version: 3.0.3
475
509
  signing_key:
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Osso
4
- module Helpers
5
- module Auth
6
- END_USER_SCOPE = 'end-user'
7
- INTERNAL_SCOPE = 'internal'
8
- ADMIN_SCOPE = 'admin'
9
-
10
- attr_accessor :current_user
11
-
12
- def token_protected!
13
- decode(token)
14
- rescue JWT::DecodeError
15
- halt 401
16
- end
17
-
18
- def enterprise_protected!(domain = nil)
19
- return if admin_authorized?
20
- return if internal_authorized?
21
- return if enterprise_authorized?(domain)
22
-
23
- halt 401 if request.post?
24
-
25
- redirect ENV['JWT_URL']
26
- end
27
-
28
- def internal_protected!
29
- return if admin_authorized?
30
- return if internal_authorized?
31
-
32
- redirect ENV['JWT_URL']
33
- end
34
-
35
- def admin_protected!
36
- return true if admin_authorized?
37
-
38
- redirect ENV['JWT_URL']
39
- end
40
-
41
- private
42
-
43
- def enterprise_authorized?(domain)
44
- decode(token)
45
-
46
- @current_user[:scope] == END_USER_SCOPE &&
47
- @current_user[:email].split('@')[1] == domain
48
- rescue JWT::DecodeError
49
- false
50
- end
51
-
52
- def internal_authorized?
53
- decode(token)
54
-
55
- @current_user[:scope] == INTERNAL_SCOPE
56
- rescue JWT::DecodeError
57
- false
58
- end
59
-
60
- def admin_authorized?
61
- decode(token)
62
-
63
- @current_user[:scope] == ADMIN_SCOPE
64
- rescue JWT::DecodeError
65
- false
66
- end
67
-
68
- def token
69
- session['admin_token'] || request.env['HTTP_AUTHORIZATION'] || request.params['admin_token']
70
- end
71
-
72
- def chomp_token
73
- return unless request['admin_token'].present?
74
-
75
- session['admin_token'] = request['admin_token']
76
-
77
- return if request.post?
78
-
79
- redirect request.path
80
- end
81
-
82
- def decode(token)
83
- payload, _args = JWT.decode(
84
- token,
85
- ENV['JWT_HMAC_SECRET'],
86
- true,
87
- { algorithm: 'HS256' },
88
- )
89
-
90
- @current_user = payload.symbolize_keys
91
- end
92
- end
93
- end
94
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Osso
4
- module Helpers
5
- end
6
- end
7
-
8
- require_relative 'auth'
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe 'App' do
6
- end