osso 0.0.3.2 → 0.0.3.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +4 -1
  3. data/.rubocop.yml +1 -2
  4. data/Gemfile.lock +3 -1
  5. data/bin/console +4 -3
  6. data/config/database.yml +2 -2
  7. data/db/schema.rb +133 -1
  8. data/lib/osso.rb +2 -0
  9. data/lib/osso/db/migrate/20200328143305_create_identity_providers.rb +12 -0
  10. data/lib/osso/db/migrate/20200411184535_add_provider_id_to_users.rb +2 -2
  11. data/lib/osso/db/migrate/20200411192645_create_enterprise_accounts.rb +1 -1
  12. data/lib/osso/db/migrate/20200502135008_add_oauth_client_id_to_enterprise_accounts_and_identity_providers.rb +6 -0
  13. data/lib/osso/db/migrate/20200714223226_add_identity_provider_service_enum.rb +17 -0
  14. data/lib/osso/db/migrate/20200715154211_rename_idp_fields_on_identity_provider_to_sso.rb +6 -0
  15. data/lib/osso/db/migrate/20200715205801_add_name_to_enterprise_account.rb +5 -0
  16. data/lib/osso/graphql/mutation.rb +10 -5
  17. data/lib/osso/graphql/mutations.rb +5 -2
  18. data/lib/osso/graphql/mutations/base_mutation.rb +35 -14
  19. data/lib/osso/graphql/mutations/configure_identity_provider.rb +31 -21
  20. data/lib/osso/graphql/mutations/create_enterprise_account.rb +25 -0
  21. data/lib/osso/graphql/mutations/create_identity_provider.rb +22 -16
  22. data/lib/osso/graphql/mutations/set_identity_provider.rb +27 -0
  23. data/lib/osso/graphql/query.rb +19 -22
  24. data/lib/osso/graphql/resolvers.rb +5 -1
  25. data/lib/osso/graphql/resolvers/enterprise_account.rb +16 -12
  26. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +10 -6
  27. data/lib/osso/graphql/resolvers/oauth_clients.rb +9 -5
  28. data/lib/osso/graphql/schema.rb +27 -19
  29. data/lib/osso/graphql/types.rb +4 -1
  30. data/lib/osso/graphql/types/base_enum.rb +6 -2
  31. data/lib/osso/graphql/types/base_input_object.rb +10 -0
  32. data/lib/osso/graphql/types/base_object.rb +6 -2
  33. data/lib/osso/graphql/types/enterprise_account.rb +22 -18
  34. data/lib/osso/graphql/types/identity_provider.rb +26 -25
  35. data/lib/osso/graphql/types/identity_provider_service.rb +8 -4
  36. data/lib/osso/graphql/types/oauth_client.rb +13 -9
  37. data/lib/osso/graphql/types/user.rb +10 -5
  38. data/lib/osso/helpers/auth.rb +5 -3
  39. data/lib/osso/lib/app_config.rb +1 -1
  40. data/lib/osso/lib/route_map.rb +28 -0
  41. data/lib/osso/models/enterprise_account.rb +4 -4
  42. data/lib/osso/models/identity_provider.rb +48 -0
  43. data/lib/osso/models/models.rb +1 -1
  44. data/lib/osso/models/oauth_client.rb +2 -2
  45. data/lib/osso/models/saml_provider.rb +13 -16
  46. data/lib/osso/models/saml_providers/azure_saml_provider.rb +2 -2
  47. data/lib/osso/models/saml_providers/okta_saml_provider.rb +1 -1
  48. data/lib/osso/models/user.rb +3 -3
  49. data/lib/osso/routes/admin.rb +18 -15
  50. data/lib/osso/routes/auth.rb +30 -27
  51. data/lib/osso/routes/oauth.rb +50 -45
  52. data/lib/osso/version.rb +1 -1
  53. data/osso-rb.gemspec +3 -3
  54. data/spec/factories/enterprise_account.rb +5 -4
  55. data/spec/factories/identity_providers.rb +49 -0
  56. data/spec/factories/user.rb +1 -1
  57. data/spec/graphql/mutations/configure_identity_provider_spec.rb +75 -0
  58. data/spec/graphql/mutations/create_enterprise_account_spec.rb +68 -0
  59. data/spec/graphql/mutations/create_identity_provider_spec.rb +104 -0
  60. data/spec/graphql/query/enterprise_account_spec.rb +68 -0
  61. data/spec/graphql/query/enterprise_accounts_spec.rb +44 -0
  62. data/spec/graphql/query/identity_provider_spec.rb +65 -0
  63. data/spec/graphql/query/oauth_clients_account_spec.rb +48 -0
  64. data/spec/models/azure_saml_provider_spec.rb +14 -14
  65. data/spec/models/identity_provider_spec.rb +17 -0
  66. data/spec/models/okta_saml_provider_spec.rb +15 -15
  67. data/spec/routes/admin_spec.rb +2 -0
  68. data/spec/routes/auth_spec.rb +9 -9
  69. data/spec/routes/oauth_spec.rb +1 -1
  70. data/spec/spec_helper.rb +4 -5
  71. data/spec/support/spec_app.rb +9 -0
  72. data/spec/support/views/admin.erb +5 -0
  73. metadata +37 -12
  74. data/lib/osso/db/migrate/20200411144528_create_saml_providers.rb +0 -13
  75. data/lib/osso/db/migrate/20200413153029_add_oauth_client_reference_to_saml_providers.rb +0 -5
  76. data/lib/osso/db/migrate/20200501203026_drop_null_constraints_from_saml_provider.rb +0 -7
  77. data/lib/osso/db/migrate/20200501204047_drop_acs_url.rb +0 -5
  78. data/lib/osso/db/migrate/20200502135008_add_oauth_client_id_to_enterprise_account.rb +0 -5
  79. data/lib/osso/db/migrate/20200601131227_drop_null_constraint_from_saml_providers_provider.rb +0 -7
  80. data/lib/osso/db/schema.rb +0 -132
  81. data/lib/osso/graphql/mutations/set_saml_provider.rb +0 -23
  82. data/spec/factories/saml_providers.rb +0 -46
  83. data/spec/models/saml_provider_spec.rb +0 -31
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Types
4
- class IdentityProviderService < BaseEnum
5
- value('AZURE', 'Microsoft Azure Identity Provider', value: 'Osso::Models::AzureSamlProvider')
6
- value('OKTA', 'Okta Identity Provider', value: 'Osso::Models::OktaSamlProvider')
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class IdentityProviderService < BaseEnum
7
+ value('AZURE', 'Microsoft Azure Identity Provider', value: 'Osso::Models::AzureSamlProvider')
8
+ value('OKTA', 'Okta Identity Provider', value: 'Osso::Models::OktaSamlProvider')
9
+ end
10
+ end
7
11
  end
8
12
  end
@@ -2,15 +2,19 @@
2
2
 
3
3
  require 'graphql'
4
4
 
5
- module Types
6
- class OAuthClient < Types::BaseObject
7
- description 'An OAuth client used to consume Osso SAML users'
8
- implements GraphQL::Types::Relay::Node
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class OAuthClient < Types::BaseObject
9
+ description 'An OAuth client used to consume Osso SAML users'
10
+ implements ::GraphQL::Types::Relay::Node
9
11
 
10
- global_id_field :gid
11
- field :id, ID, null: false
12
- field :name, String, null: false
13
- field :client_id, String, null: false
14
- field :client_secret, String, null: false
12
+ global_id_field :gid
13
+ field :id, ID, null: false
14
+ field :name, String, null: false
15
+ field :client_id, String, null: false
16
+ field :client_secret, String, null: false
17
+ end
18
+ end
15
19
  end
16
20
  end
@@ -2,11 +2,16 @@
2
2
 
3
3
  require 'graphql'
4
4
  require_relative 'base_object'
5
- module Types
6
- class User < Types::BaseObject
7
- description 'A User of the application'
8
5
 
9
- field :id, ID, null: false
10
- field :name, String, null: true
6
+ module Osso
7
+ module GraphQL
8
+ module Types
9
+ class User < Types::BaseObject
10
+ description 'A User of the application'
11
+
12
+ field :id, ID, null: false
13
+ field :name, String, null: true
14
+ end
15
+ end
11
16
  end
12
17
  end
@@ -4,15 +4,17 @@ module Osso
4
4
  module Helpers
5
5
  module Auth
6
6
  attr_accessor :current_scope
7
-
7
+
8
8
  def enterprise_protected!(domain = nil)
9
9
  return if admin_authorized?
10
10
  return if enterprise_authorized?(domain)
11
11
 
12
+ halt 401 if request.post?
13
+
12
14
  redirect ENV['JWT_URL']
13
15
  end
14
16
 
15
- def enterprise_authorized?(domain)
17
+ def enterprise_authorized?(_domain)
16
18
  payload, _args = JWT.decode(
17
19
  token,
18
20
  ENV['JWT_HMAC_SECRET'],
@@ -66,4 +68,4 @@ module Osso
66
68
  end
67
69
  end
68
70
  end
69
- end
71
+ 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.fetch('SESSION_SECRET')
10
+ use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
11
11
 
12
12
  error ActiveRecord::RecordNotFound do
13
13
  status 404
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/MethodLength
4
+
5
+ module Osso
6
+ module RouteMap
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ use Osso::Admin
10
+ use Osso::Auth
11
+ use Osso::Oauth
12
+
13
+ post '/graphql' do
14
+ enterprise_protected!
15
+
16
+ result = Osso::GraphQL::Schema.execute(
17
+ params[:query],
18
+ variables: params[:variables],
19
+ context: { scope: current_scope },
20
+ )
21
+
22
+ json result
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ # rubocop:enable Metrics/MethodLength
@@ -10,19 +10,19 @@ module Osso
10
10
  class EnterpriseAccount < ActiveRecord::Base
11
11
  belongs_to :oauth_client
12
12
  has_many :users
13
- has_many :saml_providers
13
+ has_many :identity_providers
14
14
 
15
15
  def single_provider?
16
- saml_providers.one?
16
+ identity_providers.one?
17
17
  end
18
18
 
19
19
  def provider
20
20
  return nil unless single_provider?
21
21
 
22
- saml_providers.first
22
+ identity_providers.first
23
23
  end
24
24
 
25
- alias saml_provider provider
25
+ alias identity_provider provider
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ # Base class for SAML Providers
6
+ class IdentityProvider < ActiveRecord::Base
7
+ NAME_FORMAT = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
8
+ belongs_to :enterprise_account
9
+ belongs_to :oauth_client
10
+ has_many :users
11
+
12
+ def name
13
+ service.titlecase
14
+ # raise(
15
+ # NoMethodError,
16
+ # '#name must be defined on each provider specific subclass',
17
+ # )
18
+ end
19
+
20
+ def saml_options
21
+ attributes.slice(
22
+ 'domain',
23
+ 'idp_cert',
24
+ 'idp_sso_target_url',
25
+ ).symbolize_keys
26
+ end
27
+
28
+ # def saml_options
29
+ # raise(
30
+ # NoMethodError,
31
+ # '#saml_options must be defined on each provider specific subclass',
32
+ # )
33
+ # end
34
+
35
+ def assertion_consumer_service_url
36
+ [
37
+ ENV.fetch('BASE_URL'),
38
+ 'auth',
39
+ 'saml',
40
+ id,
41
+ 'callback',
42
+ ].join('/')
43
+ end
44
+
45
+ alias acs_url assertion_consumer_service_url
46
+ end
47
+ end
48
+ end
@@ -12,5 +12,5 @@ require_relative 'authorization_code'
12
12
  require_relative 'enterprise_account'
13
13
  require_relative 'oauth_client'
14
14
  require_relative 'redirect_uri'
15
- require_relative 'saml_provider'
15
+ require_relative 'identity_provider'
16
16
  require_relative 'user'
@@ -6,7 +6,7 @@ module Osso
6
6
  class OauthClient < ActiveRecord::Base
7
7
  has_many :access_tokens
8
8
  has_many :refresh_tokens
9
- has_many :saml_providers
9
+ has_many :identity_providers
10
10
  has_many :redirect_uris
11
11
 
12
12
  before_validation :setup, on: :create
@@ -29,4 +29,4 @@ module Osso
29
29
  end
30
30
  end
31
31
  end
32
- end
32
+ end
@@ -3,28 +3,27 @@
3
3
  module Osso
4
4
  module Models
5
5
  # Base class for SAML Providers
6
- class SamlProvider < ActiveRecord::Base
6
+ class IdentityProvider < ActiveRecord::Base
7
7
  NAME_FORMAT = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
8
- self.inheritance_column = :provider
9
8
  belongs_to :enterprise_account
10
9
  belongs_to :oauth_client
11
10
  has_many :users
12
11
 
13
12
  before_create :create_enterprise_account
14
13
 
15
- def name
16
- raise(
17
- NoMethodError,
18
- '#name must be defined on each provider specific subclass',
19
- )
20
- end
14
+ # def name
15
+ # raise(
16
+ # NoMethodError,
17
+ # '#name must be defined on each provider specific subclass',
18
+ # )
19
+ # end
21
20
 
22
- def saml_options
23
- raise(
24
- NoMethodError,
25
- '#saml_options must be defined on each provider specific subclass',
26
- )
27
- end
21
+ # def saml_options
22
+ # raise(
23
+ # NoMethodError,
24
+ # '#saml_options must be defined on each provider specific subclass',
25
+ # )
26
+ # end
28
27
 
29
28
  def assertion_consumer_service_url
30
29
  [
@@ -48,5 +47,3 @@ module Osso
48
47
  end
49
48
  end
50
49
  end
51
- require_relative 'saml_providers/azure_saml_provider'
52
- require_relative 'saml_providers/okta_saml_provider'
@@ -3,7 +3,7 @@
3
3
  module Osso
4
4
  module Models
5
5
  # Subclass for Azure / ADFS IDP instances
6
- class AzureSamlProvider < Models::SamlProvider
6
+ class AzureSamlProvider < Models::IdentityProvider
7
7
  def name
8
8
  'Azure'
9
9
  end
@@ -19,4 +19,4 @@ module Osso
19
19
  end
20
20
  end
21
21
  end
22
- end
22
+ end
@@ -3,7 +3,7 @@
3
3
  module Osso
4
4
  module Models
5
5
  # Subclass for Okta IDP instances
6
- class OktaSamlProvider < Models::SamlProvider
6
+ class OktaSamlProvider < Models::IdentityProvider
7
7
  def name
8
8
  'Okta'
9
9
  end
@@ -4,19 +4,19 @@ module Osso
4
4
  module Models
5
5
  class User < ActiveRecord::Base
6
6
  belongs_to :enterprise_account
7
- belongs_to :saml_provider
7
+ belongs_to :identity_provider
8
8
  has_many :authorization_codes, dependent: :delete_all
9
9
  has_many :access_tokens, dependent: :delete_all
10
10
 
11
11
  def oauth_client
12
- saml_provider.oauth_client
12
+ identity_provider.oauth_client
13
13
  end
14
14
 
15
15
  def as_json(*)
16
16
  {
17
17
  email: email,
18
18
  id: id,
19
- idp: saml_provider.name,
19
+ idp: identity_provider.name,
20
20
  }
21
21
  end
22
22
  end
@@ -6,33 +6,36 @@ module Osso
6
6
  class Admin < Sinatra::Base
7
7
  include AppConfig
8
8
  helpers Helpers::Auth
9
+ register Sinatra::Namespace
9
10
 
10
11
  before do
11
12
  chomp_token
12
13
  end
13
14
 
14
- get '/' do
15
- admin_protected!
15
+ namespace '/admin' do
16
+ get '' do
17
+ admin_protected!
16
18
 
17
- erb :admin
18
- end
19
+ erb :admin
20
+ end
19
21
 
20
- get '/enterprise' do
21
- admin_protected!
22
+ get '/enterprise' do
23
+ admin_protected!
22
24
 
23
- erb :admin
24
- end
25
+ erb :admin
26
+ end
25
27
 
26
- get '/enterprise/:domain' do
27
- enterprise_protected!(params[:domain])
28
+ get '/enterprise/:domain' do
29
+ enterprise_protected!(params[:domain])
28
30
 
29
- erb :admin
30
- end
31
+ erb :admin
32
+ end
31
33
 
32
- get '/config' do
33
- admin_protected!
34
+ get '/config' do
35
+ admin_protected!
34
36
 
35
- erb :admin
37
+ erb :admin
38
+ end
36
39
  end
37
40
  end
38
41
  end
@@ -8,6 +8,7 @@ require 'omniauth-saml'
8
8
  module Osso
9
9
  class Auth < Sinatra::Base
10
10
  include AppConfig
11
+ register Sinatra::Namespace
11
12
 
12
13
  UUID_REGEXP =
13
14
  /[0-9a-f]{8}-[0-9a-f]{3,4}-[0-9a-f]{4}-[0-9a-f]{3,4}-[0-9a-f]{12}/.
@@ -24,41 +25,43 @@ module Osso
24
25
  identity_provider_id_regex: UUID_REGEXP,
25
26
  path_prefix: '/saml',
26
27
  callback_suffix: 'callback',
27
- ) do |saml_provider_id, _env|
28
- provider = Models::SamlProvider.find(saml_provider_id)
28
+ ) do |identity_provider_id, _env|
29
+ provider = Models::IdentityProvider.find(identity_provider_id)
29
30
  provider.saml_options
30
31
  end
31
32
  end
32
33
 
33
- # Enterprise users are sent here after authenticating against
34
- # their Identity Provider. We find or create a user record,
35
- # and then create an authorization code for that user. The user
36
- # is redirected back to your application with this code
37
- # as a URL query param, which you then exhange for an access token
38
- post '/saml/:id/callback' do
39
- provider = Models::SamlProvider.find(params[:id])
40
- oauth_client = provider.oauth_client
41
- redirect_uri = env['redirect_uri'] || oauth_client.default_redirect_uri.uri
34
+ namespace '/auth' do
35
+ # Enterprise users are sent here after authenticating against
36
+ # their Identity Provider. We find or create a user record,
37
+ # and then create an authorization code for that user. The user
38
+ # is redirected back to your application with this code
39
+ # as a URL query param, which you then exhange for an access token
40
+ post '/saml/:id/callback' do
41
+ provider = Models::IdentityProvider.find(params[:id])
42
+ oauth_client = provider.oauth_client
43
+ redirect_uri = env['redirect_uri'] || oauth_client.default_redirect_uri.uri
42
44
 
43
- attributes = env['omniauth.auth']&.
44
- extra&.
45
- response_object&.
46
- attributes
45
+ attributes = env['omniauth.auth']&.
46
+ extra&.
47
+ response_object&.
48
+ attributes
47
49
 
48
- user = Models::User.where(
49
- email: attributes[:email],
50
- idp_id: attributes[:id],
51
- ).first_or_create! do |new_user|
52
- new_user.enterprise_account_id = provider.enterprise_account_id
53
- new_user.saml_provider_id = provider.id
54
- end
50
+ user = Models::User.where(
51
+ email: attributes[:email],
52
+ idp_id: attributes[:id],
53
+ ).first_or_create! do |new_user|
54
+ new_user.enterprise_account_id = provider.enterprise_account_id
55
+ new_user.identity_provider_id = provider.id
56
+ end
55
57
 
56
- authorization_code = user.authorization_codes.create!(
57
- oauth_client: oauth_client,
58
- redirect_uri: redirect_uri,
59
- )
58
+ authorization_code = user.authorization_codes.create!(
59
+ oauth_client: oauth_client,
60
+ redirect_uri: redirect_uri,
61
+ )
60
62
 
61
- redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{session[:oauth_state]}")
63
+ redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{session[:oauth_state]}")
64
+ end
62
65
  end
63
66
  end
64
67
  end