osso 0.0.6.alpha → 0.0.11

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 (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