osso 0.0.3.14 → 0.0.3.19

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +17 -1
  3. data/.rubocop.yml +1 -0
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +10 -2
  6. data/README.md +3 -2
  7. data/bin/publish +18 -0
  8. data/db/schema.rb +9 -1
  9. data/lib/osso/db/migrate/20200826201852_create_app_config.rb +11 -0
  10. data/lib/osso/graphql/mutation.rb +7 -0
  11. data/lib/osso/graphql/mutations.rb +2 -0
  12. data/lib/osso/graphql/mutations/base_mutation.rb +18 -5
  13. data/lib/osso/graphql/mutations/configure_identity_provider.rb +8 -10
  14. data/lib/osso/graphql/mutations/create_enterprise_account.rb +7 -0
  15. data/lib/osso/graphql/mutations/create_identity_provider.rb +14 -5
  16. data/lib/osso/graphql/mutations/create_oauth_client.rb +1 -3
  17. data/lib/osso/graphql/mutations/delete_enterprise_account.rb +9 -11
  18. data/lib/osso/graphql/mutations/delete_oauth_client.rb +1 -3
  19. data/lib/osso/graphql/mutations/regenerate_oauth_credentials.rb +1 -3
  20. data/lib/osso/graphql/mutations/set_redirect_uris.rb +2 -4
  21. data/lib/osso/graphql/mutations/update_app_config.rb +30 -0
  22. data/lib/osso/graphql/query.rb +14 -0
  23. data/lib/osso/graphql/resolvers.rb +1 -0
  24. data/lib/osso/graphql/resolvers/base_resolver.rb +21 -0
  25. data/lib/osso/graphql/resolvers/enterprise_account.rb +1 -11
  26. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +2 -2
  27. data/lib/osso/graphql/resolvers/oauth_clients.rb +2 -2
  28. data/lib/osso/graphql/types.rb +2 -1
  29. data/lib/osso/graphql/types/admin_user.rb +22 -0
  30. data/lib/osso/graphql/types/app_config.rb +22 -0
  31. data/lib/osso/graphql/types/base_object.rb +22 -0
  32. data/lib/osso/graphql/types/enterprise_account.rb +0 -5
  33. data/lib/osso/graphql/types/identity_provider.rb +0 -6
  34. data/lib/osso/graphql/types/oauth_client.rb +2 -4
  35. data/lib/osso/graphql/types/redirect_uri.rb +2 -4
  36. data/lib/osso/helpers/auth.rb +40 -18
  37. data/lib/osso/lib/route_map.rb +2 -2
  38. data/lib/osso/models/app_config.rb +33 -0
  39. data/lib/osso/models/identity_provider.rb +6 -12
  40. data/lib/osso/models/models.rb +1 -0
  41. data/lib/osso/models/oauth_client.rb +1 -0
  42. data/lib/osso/models/redirect_uri.rb +0 -11
  43. data/lib/osso/routes/admin.rb +2 -2
  44. data/lib/osso/routes/auth.rb +29 -12
  45. data/lib/osso/routes/oauth.rb +25 -18
  46. data/lib/osso/version.rb +1 -1
  47. data/lib/tasks/bootstrap.rake +2 -0
  48. data/spec/graphql/mutations/configure_identity_provider_spec.rb +17 -4
  49. data/spec/graphql/mutations/create_enterprise_account_spec.rb +53 -4
  50. data/spec/graphql/mutations/create_identity_provider_spec.rb +18 -6
  51. data/spec/graphql/mutations/create_oauth_client_spec.rb +10 -3
  52. data/spec/graphql/mutations/delete_enterprise_account_spec.rb +18 -4
  53. data/spec/graphql/mutations/delete_oauth_client_spec.rb +8 -4
  54. data/spec/graphql/query/enterprise_account_spec.rb +21 -6
  55. data/spec/graphql/query/enterprise_accounts_spec.rb +4 -2
  56. data/spec/graphql/query/identity_provider_spec.rb +16 -6
  57. data/spec/graphql/query/oauth_clients_spec.rb +10 -7
  58. data/spec/helpers/auth_spec.rb +97 -0
  59. data/spec/models/identity_provider_spec.rb +12 -0
  60. data/spec/routes/auth_spec.rb +18 -0
  61. data/spec/routes/oauth_spec.rb +5 -2
  62. data/spec/spec_helper.rb +3 -0
  63. data/spec/support/views/error.erb +0 -0
  64. metadata +15 -6
  65. data/lib/osso/graphql/types/user.rb +0 -17
@@ -4,17 +4,6 @@ module Osso
4
4
  module Models
5
5
  class RedirectUri < ActiveRecord::Base
6
6
  belongs_to :oauth_client
7
-
8
- # TODO
9
- # before_validation :set_primary, on: :creaet, :update
10
-
11
- private
12
-
13
- def set_primary
14
- if primary_was.true? && primary.false?
15
-
16
- end
17
- end
18
7
  end
19
8
  end
20
9
  end
@@ -14,13 +14,13 @@ module Osso
14
14
 
15
15
  namespace '/admin' do
16
16
  get '' do
17
- admin_protected!
17
+ internal_protected!
18
18
 
19
19
  erb :admin
20
20
  end
21
21
 
22
22
  get '/enterprise' do
23
- admin_protected!
23
+ internal_protected!
24
24
 
25
25
  erb :admin
26
26
  end
@@ -14,10 +14,6 @@ module Osso
14
14
  /[0-9a-f]{8}-[0-9a-f]{3,4}-[0-9a-f]{4}-[0-9a-f]{3,4}-[0-9a-f]{12}/.
15
15
  freeze
16
16
 
17
- def self.internal_redirect?(env)
18
- env['HTTP_REFERER']&.match(env['SERVER_NAME'])
19
- end
20
-
21
17
  use OmniAuth::Builder do
22
18
  OmniAuth::MultiProvider.register(
23
19
  self,
@@ -26,21 +22,24 @@ module Osso
26
22
  path_prefix: '/auth/saml',
27
23
  callback_suffix: 'callback',
28
24
  ) do |identity_provider_id, _env|
29
- provider = Models::IdentityProvider.find(identity_provider_id)
30
- provider.saml_options
25
+ Models::IdentityProvider.find(identity_provider_id).
26
+ saml_options
31
27
  end
32
28
  end
33
29
 
34
- namespace '/auth' do
30
+ namespace '/auth' do # rubocop:disable Metrics/BlockLength
31
+ get '/failure' do
32
+ @error = params[:message]
33
+ erb :error
34
+ end
35
35
  # Enterprise users are sent here after authenticating against
36
36
  # their Identity Provider. We find or create a user record,
37
37
  # and then create an authorization code for that user. The user
38
38
  # is redirected back to your application with this code
39
- # as a URL query param, which you then exhange for an access token
39
+ # as a URL query param, which you then exchange for an access token.
40
40
  post '/saml/:id/callback' do
41
41
  provider = Models::IdentityProvider.find(params[:id])
42
- oauth_client = provider.oauth_client
43
- redirect_uri = env['redirect_uri'] || oauth_client.primary_redirect_uri.uri
42
+ @oauth_client = provider.oauth_client
44
43
 
45
44
  attributes = env['omniauth.auth']&.
46
45
  extra&.
@@ -56,11 +55,29 @@ module Osso
56
55
  end
57
56
 
58
57
  authorization_code = user.authorization_codes.create!(
59
- oauth_client: oauth_client,
58
+ oauth_client: @oauth_client,
60
59
  redirect_uri: redirect_uri,
61
60
  )
62
61
 
63
- redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{session[:oauth_state]}")
62
+ # Mark IDP as active
63
+
64
+ redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{provider_state}")
65
+ end
66
+
67
+ def redirect_uri
68
+ return @oauth_client.primary_redirect_uri.uri if valid_idp_initiated_flow
69
+
70
+ session[:osso_oauth_redirect_uri]
71
+ end
72
+
73
+ def provider_state
74
+ return @provider_state = 'IDP_INITIATED' if valid_idp_initiated_flow
75
+
76
+ session.delete(:osso_oauth_state)
77
+ end
78
+
79
+ def valid_idp_initiated_flow
80
+ !session[:osso_oauth_redirect_uri] && !session[:osso_oauth_state]
64
81
  end
65
82
  end
66
83
  end
@@ -6,38 +6,45 @@ module Osso
6
6
  class Oauth < Sinatra::Base
7
7
  include AppConfig
8
8
  register Sinatra::Namespace
9
- # rubocop:disable Metrics/BlockLength
10
- namespace '/oauth' do
9
+
10
+ namespace '/oauth' do # rubocop:disable Metrics/BlockLength
11
11
  # Send your users here in order to being an authentication
12
12
  # flow. This flow follows the authorization grant oauth
13
13
  # spec with one exception - you must also pass the domain
14
- # of the user who wants to sign in.
14
+ # of the user who wants to sign in. If the sign in request
15
+ # is valid, the user is redirected to their Identity Provider.
16
+ # Once they complete IdP login, they will be returned to the
17
+ # redirect_uri with an authorization code parameter.
15
18
  get '/authorize' do
16
- @enterprise = Models::EnterpriseAccount.
17
- includes(:identity_providers).
18
- find_by!(domain: params[:domain])
19
-
20
19
  Rack::OAuth2::Server::Authorize.new do |req, _res|
21
20
  client = Models::OauthClient.find_by!(identifier: req.client_id)
22
- req.verify_redirect_uri!(client.redirect_uri_values)
21
+ session[:osso_oauth_redirect_uri] = req.verify_redirect_uri!(client.redirect_uri_values)
22
+ session[:osso_oauth_state] = params[:state]
23
23
  end.call(env)
24
24
 
25
- if @enterprise.single_provider?
26
- session[:oauth_state] = params[:state]
27
- redirect "/auth/saml/#{@enterprise.provider.id}"
28
- end
25
+ enterprise = Models::EnterpriseAccount.
26
+ includes(:identity_providers).
27
+ find_by!(domain: params[:domain])
28
+
29
+ redirect "/auth/saml/#{enterprise.provider.id}" if enterprise.single_provider?
29
30
 
30
31
  # TODO: multiple provider support
31
32
  # erb :multiple_providers
32
33
 
33
34
  rescue Rack::OAuth2::Server::Authorize::BadRequest => e
34
35
  @error = e
35
- return erb :error
36
+ erb :error
37
+ rescue ActiveRecord::RecordNotFound => e
38
+ @error = e
39
+ @error = 'No OAuth Client exists for the provided client_id' if e.model == 'Osso::Models::OauthClient'
40
+ @error = "No Customer exists with the domain #{params[:domain]}" if e.model == 'Osso::Models::EnterpriseAccount'
41
+ erb :error
36
42
  end
37
43
 
38
- # Exchange an authorization code token for an access token.
39
- # In addition to the token, you must include all paramaters
40
- # required by Oauth spec: redirect_uri, client ID, and client secret
44
+ # Exchange an authorization code for an access token.
45
+ # In addition to the authorization code, you must include all
46
+ # paramaters required by OAuth spec: redirect_uri, client ID,
47
+ # and client secret
41
48
  post '/token' do
42
49
  Rack::OAuth2::Server::Token.new do |req, res|
43
50
  code = Models::AuthorizationCode.
@@ -49,7 +56,8 @@ module Osso
49
56
  end.call(env)
50
57
  end
51
58
 
52
- # Use the access token to request a user profile
59
+ # Use the access token to request a profile for the user who
60
+ # just logged in. Access tokens are short-lived.
53
61
  get '/me' do
54
62
  json Models::AccessToken.
55
63
  includes(:user).
@@ -60,4 +68,3 @@ module Osso
60
68
  end
61
69
  end
62
70
  end
63
- # rubocop:enable Metrics/BlockLength
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Osso
4
- VERSION = '0.0.3.14'
4
+ VERSION = '0.0.3.19'
5
5
  end
@@ -12,5 +12,7 @@ namespace :osso do
12
12
  name: environement,
13
13
  )
14
14
  end
15
+
16
+ Osso::Models::AppConfig.create!
15
17
  end
16
18
  end
@@ -39,12 +39,15 @@ describe Osso::GraphQL::Schema do
39
39
  described_class.execute(
40
40
  mutation,
41
41
  variables: variables,
42
- context: { scope: current_scope },
42
+ context: current_context,
43
43
  )
44
44
  end
45
45
 
46
46
  describe 'for an admin user' do
47
- let(:current_scope) { :admin }
47
+ let(:current_context) do
48
+ { scope: 'admin' }
49
+ end
50
+
48
51
  it 'configures an identity provider' do
49
52
  expect(subject.dig('data', 'configureIdentityProvider', 'identityProvider', 'status')).
50
53
  to eq('Configured')
@@ -53,7 +56,12 @@ describe Osso::GraphQL::Schema do
53
56
 
54
57
  describe 'for an email scoped user' do
55
58
  let(:domain) { Faker::Internet.domain_name }
56
- let(:current_scope) { domain }
59
+ let(:current_context) do
60
+ {
61
+ scope: 'end-user',
62
+ email: "user@#{domain}",
63
+ }
64
+ end
57
65
  let(:enterprise_account) { create(:enterprise_account, domain: domain) }
58
66
  let(:identity_provider) { create(:identity_provider, enterprise_account: enterprise_account, domain: domain) }
59
67
 
@@ -65,7 +73,12 @@ describe Osso::GraphQL::Schema do
65
73
 
66
74
  describe 'for the wrong email scoped user' do
67
75
  let(:domain) { Faker::Internet.domain_name }
68
- let(:current_scope) { domain }
76
+ let(:current_context) do
77
+ {
78
+ scope: 'end-user',
79
+ email: "user@#{domain}",
80
+ }
81
+ end
69
82
 
70
83
  it 'does not configure an identity provider' do
71
84
  expect(subject.dig('errors')).to_not be_empty
@@ -5,6 +5,7 @@ require 'spec_helper'
5
5
  describe Osso::GraphQL::Schema do
6
6
  describe 'CreateIdentityProvider' do
7
7
  let(:domain) { Faker::Internet.domain_name }
8
+ let!(:oauth_client) { create(:oauth_client) }
8
9
  let(:variables) do
9
10
  {
10
11
  input: {
@@ -33,30 +34,78 @@ describe Osso::GraphQL::Schema do
33
34
  described_class.execute(
34
35
  mutation,
35
36
  variables: variables,
36
- context: { scope: current_scope },
37
+ context: current_context,
37
38
  )
38
39
  end
39
40
 
40
41
  describe 'for an admin user' do
41
- let(:current_scope) { :admin }
42
+ let(:current_context) do
43
+ { scope: 'admin' }
44
+ end
45
+ let(:variables) do
46
+ {
47
+ input: {
48
+ name: Faker::Company.name,
49
+ domain: domain,
50
+ oauthClientId: oauth_client.id,
51
+ },
52
+ }
53
+ end
54
+
42
55
  it 'creates an Enterprise Account' do
43
56
  expect { subject }.to change { Osso::Models::EnterpriseAccount.count }.by(1)
44
57
  expect(subject.dig('data', 'createEnterpriseAccount', 'enterpriseAccount', 'domain')).
45
58
  to eq(domain)
46
59
  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
+ end
65
+
66
+ describe 'for an internal scoped user' do
67
+ let(:current_context) do
68
+ {
69
+ scope: 'internal',
70
+ email: 'user@saasco.com',
71
+ oauth_client_id: oauth_client.identifier,
72
+ }
73
+ end
74
+
75
+ it 'creates an Enterprise Account' do
76
+ expect { subject }.to change { Osso::Models::EnterpriseAccount.count }.by(1)
77
+ expect(subject.dig('data', 'createEnterpriseAccount', 'enterpriseAccount', 'domain')).
78
+ to eq(domain)
79
+ 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
47
84
  end
48
85
 
49
86
  describe 'for an email scoped user' do
50
- let(:current_scope) { domain }
87
+ let(:current_context) do
88
+ {
89
+ scope: 'end-user',
90
+ email: "user@#{domain}",
91
+ oauth_client_id: oauth_client.identifier,
92
+ }
93
+ end
51
94
 
52
95
  it 'creates an Enterprise Account' do
53
96
  expect { subject }.to change { Osso::Models::EnterpriseAccount.count }.by(1)
54
97
  expect(subject.dig('data', 'createEnterpriseAccount', 'enterpriseAccount', 'domain')).
55
98
  to eq(domain)
56
99
  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
57
104
  end
58
105
  describe 'for the wrong email scoped user' do
59
- let(:current_scope) { 'foo.com' }
106
+ let(:current_context) do
107
+ { scope: 'end-user', email: 'user@foo.com' }
108
+ end
60
109
 
61
110
  it 'does not create an Enterprise Account' do
62
111
  expect { subject }.to_not(change { Osso::Models::EnterpriseAccount.count })
@@ -25,12 +25,14 @@ describe Osso::GraphQL::Schema do
25
25
  described_class.execute(
26
26
  mutation,
27
27
  variables: variables,
28
- context: { scope: current_scope },
28
+ context: current_context,
29
29
  )
30
30
  end
31
31
 
32
32
  describe 'for an admin user' do
33
- let(:current_scope) { :admin }
33
+ let(:current_context) do
34
+ { scope: 'admin' }
35
+ end
34
36
  describe 'without a service' do
35
37
  let(:variables) { { input: { enterpriseAccountId: enterprise_account.id } } }
36
38
 
@@ -54,7 +56,12 @@ describe Osso::GraphQL::Schema do
54
56
 
55
57
  describe 'for an email scoped user' do
56
58
  let(:domain) { Faker::Internet.domain_name }
57
- let(:current_scope) { domain }
59
+ let(:current_context) do
60
+ {
61
+ scope: 'end-user',
62
+ email: "user@#{domain}",
63
+ }
64
+ end
58
65
  let(:enterprise_account) { create(:enterprise_account, domain: domain) }
59
66
 
60
67
  describe 'without a service' do
@@ -80,12 +87,17 @@ describe Osso::GraphQL::Schema do
80
87
 
81
88
  describe 'for a wrong email scoped user' do
82
89
  let(:domain) { Faker::Internet.domain_name }
83
- let(:current_scope) { domain }
90
+ let(:current_context) do
91
+ {
92
+ scope: 'end-user',
93
+ email: "user@#{domain}",
94
+ }
95
+ end
84
96
  let(:enterprise_account) { create(:enterprise_account, domain: domain) }
85
97
  let(:target_account) { create(:enterprise_account) }
86
98
 
87
99
  describe 'without a service' do
88
- let(:variables) { { input: { enterpriseAccountId: target_account.id } } }
100
+ let(:variables) { { input: { enterpriseAccountId: target_account.id, domain: domain } } }
89
101
 
90
102
  it 'does not creates a identity provider' do
91
103
  expect { subject }.to_not(change { Osso::Models::IdentityProvider.count })
@@ -93,7 +105,7 @@ describe Osso::GraphQL::Schema do
93
105
  end
94
106
 
95
107
  describe 'with a service' do
96
- let(:variables) { { input: { enterpriseAccountId: target_account.id, service: 'OKTA' } } }
108
+ let(:variables) { { input: { enterpriseAccountId: target_account.id, service: 'OKTA', domain: domain } } }
97
109
 
98
110
  it 'does not creates a identity provider' do
99
111
  expect { subject }.to_not(change { Osso::Models::IdentityProvider.count })
@@ -31,12 +31,14 @@ describe Osso::GraphQL::Schema do
31
31
  described_class.execute(
32
32
  mutation,
33
33
  variables: variables,
34
- context: { scope: current_scope },
34
+ context: current_context,
35
35
  )
36
36
  end
37
37
 
38
38
  describe 'for an admin user' do
39
- let(:current_scope) { :admin }
39
+ let(:current_context) do
40
+ { scope: 'admin' }
41
+ end
40
42
  it 'creates an OauthClient' do
41
43
  expect { subject }.to change { Osso::Models::OauthClient.count }.by(1)
42
44
  expect(subject.dig('data', 'createOauthClient', 'oauthClient', 'clientId')).
@@ -45,7 +47,12 @@ describe Osso::GraphQL::Schema do
45
47
  end
46
48
 
47
49
  describe 'for an email scoped user' do
48
- let(:current_scope) { 'foo.com' }
50
+ let(:current_context) do
51
+ {
52
+ scope: 'end-user',
53
+ email: 'user@foo.com',
54
+ }
55
+ end
49
56
 
50
57
  it 'does not create an OauthClient Account' do
51
58
  expect { subject }.to_not(change { Osso::Models::OauthClient.count })
@@ -30,12 +30,15 @@ describe Osso::GraphQL::Schema do
30
30
  described_class.execute(
31
31
  mutation,
32
32
  variables: variables,
33
- context: { scope: current_scope },
33
+ context: current_context,
34
34
  )
35
35
  end
36
36
 
37
37
  describe 'for an admin user' do
38
- let(:current_scope) { :admin }
38
+ let(:current_context) do
39
+ { scope: 'admin' }
40
+ end
41
+
39
42
  it 'deletes an Enterprise Account' do
40
43
  expect { subject }.to change { Osso::Models::EnterpriseAccount.count }.by(-1)
41
44
  expect(subject.dig('data', 'createEnterpriseAccount', 'enterpriseAccount')).
@@ -44,7 +47,12 @@ describe Osso::GraphQL::Schema do
44
47
  end
45
48
 
46
49
  describe 'for an email scoped user' do
47
- let(:current_scope) { domain }
50
+ let(:current_context) do
51
+ {
52
+ scope: 'end-user',
53
+ email: "user@#{domain}",
54
+ }
55
+ end
48
56
 
49
57
  it 'deletes the Enterprise Account' do
50
58
  expect { subject }.to change { Osso::Models::EnterpriseAccount.count }.by(-1)
@@ -52,8 +60,14 @@ describe Osso::GraphQL::Schema do
52
60
  to be_nil
53
61
  end
54
62
  end
63
+
55
64
  describe 'for the wrong email scoped user' do
56
- let(:current_scope) { 'foo.com' }
65
+ let(:current_context) do
66
+ {
67
+ scope: 'end-user',
68
+ email: 'user@foo.com',
69
+ }
70
+ end
57
71
 
58
72
  it 'does not delete the Enterprise Account' do
59
73
  expect { subject }.to_not(change { Osso::Models::EnterpriseAccount.count })