osso 0.0.3.4 → 0.0.3.9

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +6 -1
  3. data/.rubocop.yml +1 -2
  4. data/Gemfile.lock +5 -1
  5. data/bin/annotate +1 -0
  6. data/bin/console +4 -3
  7. data/config/database.yml +2 -2
  8. data/db/schema.rb +90 -1
  9. data/lib/osso.rb +1 -0
  10. data/lib/osso/db/migrate/20200328143305_create_identity_providers.rb +12 -0
  11. data/lib/osso/db/migrate/20200411184535_add_provider_id_to_users.rb +2 -2
  12. data/lib/osso/db/migrate/20200411192645_create_enterprise_accounts.rb +1 -1
  13. data/lib/osso/db/migrate/20200502135008_add_oauth_client_id_to_enterprise_accounts_and_identity_providers.rb +6 -0
  14. data/lib/osso/db/migrate/20200714223226_add_identity_provider_service_enum.rb +17 -0
  15. data/lib/osso/db/migrate/20200715154211_rename_idp_fields_on_identity_provider_to_sso.rb +6 -0
  16. data/lib/osso/db/migrate/20200715205801_add_name_to_enterprise_account.rb +5 -0
  17. data/lib/osso/db/migrate/20200722230116_add_identity_provider_status_enum_and_use_on_identity_providers.rb +15 -0
  18. data/lib/osso/db/migrate/20200723153750_add_missing_timestamps.rb +35 -0
  19. data/lib/osso/db/migrate/20200723162228_drop_unneeded_tables.rb +9 -0
  20. data/lib/osso/graphql/mutation.rb +5 -2
  21. data/lib/osso/graphql/mutations.rb +5 -1
  22. data/lib/osso/graphql/mutations/base_mutation.rb +24 -7
  23. data/lib/osso/graphql/mutations/configure_identity_provider.rb +19 -13
  24. data/lib/osso/graphql/mutations/create_enterprise_account.rb +25 -0
  25. data/lib/osso/graphql/mutations/create_identity_provider.rb +9 -7
  26. data/lib/osso/graphql/mutations/create_oauth_client.rb +30 -0
  27. data/lib/osso/graphql/mutations/delete_enterprise_account.rb +34 -0
  28. data/lib/osso/graphql/mutations/delete_oauth_client.rb +30 -0
  29. data/lib/osso/graphql/query.rb +2 -2
  30. data/lib/osso/graphql/resolvers/oauth_clients.rb +2 -2
  31. data/lib/osso/graphql/schema.rb +5 -1
  32. data/lib/osso/graphql/types.rb +2 -0
  33. data/lib/osso/graphql/types/base_input_object.rb +10 -0
  34. data/lib/osso/graphql/types/base_object.rb +2 -0
  35. data/lib/osso/graphql/types/enterprise_account.rb +5 -5
  36. data/lib/osso/graphql/types/identity_provider.rb +6 -13
  37. data/lib/osso/graphql/types/identity_provider_service.rb +1 -1
  38. data/lib/osso/graphql/types/identity_provider_status.rb +14 -0
  39. data/lib/osso/graphql/types/oauth_client.rb +13 -1
  40. data/lib/osso/helpers/auth.rb +16 -15
  41. data/lib/osso/lib/app_config.rb +1 -1
  42. data/lib/osso/lib/route_map.rb +28 -0
  43. data/lib/osso/models/access_token.rb +18 -0
  44. data/lib/osso/models/authorization_code.rb +20 -0
  45. data/lib/osso/models/enterprise_account.rb +24 -4
  46. data/lib/osso/models/identity_provider.rb +77 -0
  47. data/lib/osso/models/models.rb +3 -1
  48. data/lib/osso/models/oauth_client.rb +19 -3
  49. data/lib/osso/models/redirect_uri.rb +17 -0
  50. data/lib/osso/models/user.rb +25 -3
  51. data/lib/osso/routes/admin.rb +18 -15
  52. data/lib/osso/routes/auth.rb +30 -27
  53. data/lib/osso/routes/oauth.rb +50 -45
  54. data/lib/osso/version.rb +1 -1
  55. data/osso-rb.gemspec +3 -3
  56. data/spec/factories/enterprise_account.rb +5 -4
  57. data/spec/factories/identity_providers.rb +71 -0
  58. data/spec/factories/user.rb +1 -1
  59. data/spec/graphql/mutations/configure_identity_provider_spec.rb +75 -0
  60. data/spec/graphql/mutations/create_enterprise_account_spec.rb +68 -0
  61. data/spec/graphql/mutations/create_identity_provider_spec.rb +104 -0
  62. data/spec/graphql/mutations/create_oauth_client_spec.rb +55 -0
  63. data/spec/graphql/mutations/delete_enterprise_account_spec.rb +63 -0
  64. data/spec/graphql/mutations/delete_oauth_client_spec.rb +51 -0
  65. data/spec/graphql/query/enterprise_account_spec.rb +68 -0
  66. data/spec/graphql/query/enterprise_accounts_spec.rb +44 -0
  67. data/spec/graphql/query/identity_provider_spec.rb +65 -0
  68. data/spec/graphql/query/oauth_clients_spec.rb +50 -0
  69. data/spec/models/azure_saml_provider_spec.rb +14 -14
  70. data/spec/models/identity_provider_spec.rb +17 -0
  71. data/spec/models/okta_saml_provider_spec.rb +15 -15
  72. data/spec/routes/admin_spec.rb +2 -0
  73. data/spec/routes/auth_spec.rb +9 -9
  74. data/spec/routes/oauth_spec.rb +1 -1
  75. data/spec/spec_helper.rb +4 -5
  76. data/spec/support/spec_app.rb +9 -0
  77. metadata +47 -16
  78. data/lib/osso/db/migrate/20200328143303_create_oauth_tables.rb +0 -57
  79. data/lib/osso/db/migrate/20200411144528_create_saml_providers.rb +0 -13
  80. data/lib/osso/db/migrate/20200413153029_add_oauth_client_reference_to_saml_providers.rb +0 -5
  81. data/lib/osso/db/migrate/20200501203026_drop_null_constraints_from_saml_provider.rb +0 -7
  82. data/lib/osso/db/migrate/20200501204047_drop_acs_url.rb +0 -5
  83. data/lib/osso/db/migrate/20200502135008_add_oauth_client_id_to_enterprise_account.rb +0 -5
  84. data/lib/osso/db/migrate/20200601131227_drop_null_constraint_from_saml_providers_provider.rb +0 -7
  85. data/lib/osso/db/schema.rb +0 -132
  86. data/lib/osso/graphql/mutations/set_saml_provider.rb +0 -27
  87. data/lib/osso/models/saml_provider.rb +0 -52
  88. data/lib/osso/models/saml_providers/azure_saml_provider.rb +0 -22
  89. data/lib/osso/models/saml_providers/okta_saml_provider.rb +0 -23
  90. data/spec/factories/saml_providers.rb +0 -46
  91. data/spec/models/saml_provider_spec.rb +0 -31
@@ -12,3 +12,23 @@ module Osso
12
12
  end
13
13
  end
14
14
  end
15
+
16
+ # == Schema Information
17
+ #
18
+ # Table name: authorization_codes
19
+ #
20
+ # id :uuid not null, primary key
21
+ # token :string
22
+ # redirect_uri :string
23
+ # expires_at :datetime
24
+ # created_at :datetime not null
25
+ # updated_at :datetime not null
26
+ # user_id :uuid
27
+ # oauth_client_id :uuid
28
+ #
29
+ # Indexes
30
+ #
31
+ # index_authorization_codes_on_oauth_client_id (oauth_client_id)
32
+ # index_authorization_codes_on_token (token) UNIQUE
33
+ # index_authorization_codes_on_user_id (user_id)
34
+ #
@@ -10,19 +10,39 @@ 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
29
+
30
+ # == Schema Information
31
+ #
32
+ # Table name: enterprise_accounts
33
+ #
34
+ # id :uuid not null, primary key
35
+ # domain :string not null
36
+ # external_uuid :uuid
37
+ # external_int_id :integer
38
+ # external_id :string
39
+ # oauth_client_id :uuid
40
+ # name :string not null
41
+ # created_at :datetime not null
42
+ # updated_at :datetime not null
43
+ #
44
+ # Indexes
45
+ #
46
+ # index_enterprise_accounts_on_domain (domain) UNIQUE
47
+ # index_enterprise_accounts_on_oauth_client_id (oauth_client_id)
48
+ #
@@ -0,0 +1,77 @@
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
+ before_save :set_status
12
+
13
+ def name
14
+ service.titlecase
15
+ # raise(
16
+ # NoMethodError,
17
+ # '#name must be defined on each provider specific subclass',
18
+ # )
19
+ end
20
+
21
+ def saml_options
22
+ attributes.slice(
23
+ 'domain',
24
+ 'idp_cert',
25
+ 'idp_sso_target_url',
26
+ ).symbolize_keys
27
+ end
28
+
29
+ # def saml_options
30
+ # raise(
31
+ # NoMethodError,
32
+ # '#saml_options must be defined on each provider specific subclass',
33
+ # )
34
+ # end
35
+
36
+ def assertion_consumer_service_url
37
+ [
38
+ ENV.fetch('BASE_URL'),
39
+ 'auth',
40
+ 'saml',
41
+ id,
42
+ 'callback',
43
+ ].join('/')
44
+ end
45
+
46
+ alias acs_url assertion_consumer_service_url
47
+
48
+ def set_status
49
+ return if status != 'PENDING'
50
+
51
+ self.status = 'CONFIGURED' if sso_url && sso_cert
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # == Schema Information
58
+ #
59
+ # Table name: identity_providers
60
+ #
61
+ # id :uuid not null, primary key
62
+ # service :string
63
+ # domain :string not null
64
+ # sso_url :string
65
+ # sso_cert :text
66
+ # enterprise_account_id :uuid
67
+ # oauth_client_id :uuid
68
+ # status :enum default("PENDING")
69
+ # created_at :datetime
70
+ # updated_at :datetime
71
+ #
72
+ # Indexes
73
+ #
74
+ # index_identity_providers_on_domain (domain)
75
+ # index_identity_providers_on_enterprise_account_id (enterprise_account_id)
76
+ # index_identity_providers_on_oauth_client_id (oauth_client_id)
77
+ #
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # -*- SkipSchemaAnnotations
4
+
3
5
  require 'sinatra/activerecord'
4
6
 
5
7
  module Osso
@@ -12,5 +14,5 @@ require_relative 'authorization_code'
12
14
  require_relative 'enterprise_account'
13
15
  require_relative 'oauth_client'
14
16
  require_relative 'redirect_uri'
15
- require_relative 'saml_provider'
17
+ require_relative 'identity_provider'
16
18
  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
@@ -25,8 +25,24 @@ module Osso
25
25
 
26
26
  def setup
27
27
  self.identifier = SecureRandom.hex(16)
28
- self.secret = SecureRandom.hex(64)
28
+ self.secret = SecureRandom.hex(32)
29
29
  end
30
30
  end
31
31
  end
32
- end
32
+ end
33
+
34
+ # == Schema Information
35
+ #
36
+ # Table name: oauth_clients
37
+ #
38
+ # id :uuid not null, primary key
39
+ # name :string not null
40
+ # secret :string not null
41
+ # identifier :string not null
42
+ # created_at :datetime not null
43
+ # updated_at :datetime not null
44
+ #
45
+ # Indexes
46
+ #
47
+ # index_oauth_clients_on_identifier (identifier) UNIQUE
48
+ #
@@ -18,3 +18,20 @@ module Osso
18
18
  end
19
19
  end
20
20
  end
21
+
22
+ # == Schema Information
23
+ #
24
+ # Table name: redirect_uris
25
+ #
26
+ # id :uuid not null, primary key
27
+ # uri :string not null
28
+ # primary :boolean default(FALSE), not null
29
+ # oauth_client_id :uuid
30
+ # created_at :datetime not null
31
+ # updated_at :datetime not null
32
+ #
33
+ # Indexes
34
+ #
35
+ # index_redirect_uris_on_oauth_client_id (oauth_client_id)
36
+ # index_redirect_uris_on_uri_and_primary (uri,primary) UNIQUE
37
+ #
@@ -4,21 +4,43 @@ 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
23
23
  end
24
24
  end
25
+
26
+ # == Schema Information
27
+ #
28
+ # Table name: users
29
+ #
30
+ # id :uuid not null, primary key
31
+ # email :string not null
32
+ # idp_id :string not null
33
+ # identity_provider_id :uuid
34
+ # enterprise_account_id :uuid
35
+ # created_at :datetime not null
36
+ # updated_at :datetime not null
37
+ #
38
+ # Indexes
39
+ #
40
+ # index_users_on_email_and_idp_id (email,idp_id) UNIQUE
41
+ # index_users_on_enterprise_account_id (enterprise_account_id)
42
+ #
43
+ # Foreign Keys
44
+ #
45
+ # fk_rails_... (identity_provider_id => identity_providers.id)
46
+ #
@@ -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
@@ -5,54 +5,59 @@ require 'rack/oauth2'
5
5
  module Osso
6
6
  class Oauth < Sinatra::Base
7
7
  include AppConfig
8
- # Send your users here in order to being an authentication
9
- # flow. This flow follows the authorization grant oauth
10
- # spec with one exception - you must also pass the domain
11
- # of the user who wants to sign in.
12
- get '/authorize' do
13
- @enterprise = Models::EnterpriseAccount.
14
- includes(:saml_providers).
15
- find_by!(domain: params[:domain])
16
-
17
- Rack::OAuth2::Server::Authorize.new do |req, _res|
18
- client = Models::OauthClient.find_by!(identifier: req.client_id)
19
- req.verify_redirect_uri!(client.redirect_uri_values)
20
- end.call(env)
21
-
22
- if @enterprise.single_provider?
23
- session[:oauth_state] = params[:state]
24
- redirect "/auth/saml/#{@enterprise.provider.id}"
8
+ register Sinatra::Namespace
9
+ # rubocop:disable Metrics/BlockLength
10
+ namespace '/oauth' do
11
+ # Send your users here in order to being an authentication
12
+ # flow. This flow follows the authorization grant oauth
13
+ # spec with one exception - you must also pass the domain
14
+ # of the user who wants to sign in.
15
+ get '/authorize' do
16
+ @enterprise = Models::EnterpriseAccount.
17
+ includes(:identity_providers).
18
+ find_by!(domain: params[:domain])
19
+
20
+ Rack::OAuth2::Server::Authorize.new do |req, _res|
21
+ client = Models::OauthClient.find_by!(identifier: req.client_id)
22
+ req.verify_redirect_uri!(client.redirect_uri_values)
23
+ end.call(env)
24
+
25
+ if @enterprise.single_provider?
26
+ session[:oauth_state] = params[:state]
27
+ redirect "/auth/saml/#{@enterprise.provider.id}"
28
+ end
29
+
30
+ # TODO: multiple provider support
31
+ # erb :multiple_providers
32
+
33
+ rescue Rack::OAuth2::Server::Authorize::BadRequest => e
34
+ @error = e
35
+ return erb :error
25
36
  end
26
37
 
27
- # TODO: multiple provider support
28
- # erb :multiple_providers
29
-
30
- rescue Rack::OAuth2::Server::Authorize::BadRequest => e
31
- @error = e
32
- return erb :error
33
- end
34
-
35
- # Exchange an authorization code token for an access token.
36
- # In addition to the token, you must include all paramaters
37
- # required by Oauth spec: redirect_uri, client ID, and client secret
38
- post '/token' do
39
- Rack::OAuth2::Server::Token.new do |req, res|
40
- code = Models::AuthorizationCode.
41
- find_by_token!(params[:code])
42
- client = Models::OauthClient.find_by!(identifier: req.client_id)
43
- req.invalid_client! if client.secret != req.client_secret
44
- req.invalid_grant! if code.redirect_uri != req.redirect_uri
45
- res.access_token = code.access_token.to_bearer_token
46
- end.call(env)
47
- end
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
41
+ post '/token' do
42
+ Rack::OAuth2::Server::Token.new do |req, res|
43
+ code = Models::AuthorizationCode.
44
+ find_by_token!(params[:code])
45
+ client = Models::OauthClient.find_by!(identifier: req.client_id)
46
+ req.invalid_client! if client.secret != req.client_secret
47
+ req.invalid_grant! if code.redirect_uri != req.redirect_uri
48
+ res.access_token = code.access_token.to_bearer_token
49
+ end.call(env)
50
+ end
48
51
 
49
- # Use the access token to request a user profile
50
- get '/me' do
51
- json Models::AccessToken.
52
- includes(:user).
53
- valid.
54
- find_by_token!(params[:access_token]).
55
- user
52
+ # Use the access token to request a user profile
53
+ get '/me' do
54
+ json Models::AccessToken.
55
+ includes(:user).
56
+ valid.
57
+ find_by_token!(params[:access_token]).
58
+ user
59
+ end
56
60
  end
57
61
  end
58
62
  end
63
+ # rubocop:enable Metrics/BlockLength