osso 0.0.3.4 → 0.0.3.9

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class CreateEnterpriseAccount < BaseMutation
7
+ null false
8
+
9
+ argument :domain, String, required: true
10
+ argument :name, String, required: true
11
+
12
+ field :enterprise_account, Types::EnterpriseAccount, null: false
13
+ field :errors, [String], null: false
14
+
15
+ def resolve(**args)
16
+ enterprise_account = Osso::Models::EnterpriseAccount.new(args)
17
+
18
+ return response_data(enterprise_account: enterprise_account) if enterprise_account.save
19
+
20
+ response_error(errors: enterprise_account.errors.full_messages)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -5,22 +5,24 @@ module Osso
5
5
  module Mutations
6
6
  class CreateIdentityProvider < BaseMutation
7
7
  null false
8
+
8
9
  argument :enterprise_account_id, ID, required: true
9
- argument :provider_service, Types::IdentityProviderService, required: true
10
+ argument :service, Types::IdentityProviderService, required: false
10
11
 
11
12
  field :identity_provider, Types::IdentityProvider, null: false
12
13
  field :errors, [String], null: false
13
14
 
14
- def resolve(enterprise_account_id:, provider_service:)
15
+ def resolve(enterprise_account_id:, service: nil)
15
16
  enterprise_account = Osso::Models::EnterpriseAccount.find(enterprise_account_id)
16
- identity_provider = enterprise_account.saml_providers.create!(
17
- provider: provider_service || 'OKTA',
17
+ identity_provider = enterprise_account.identity_providers.build(
18
+ enterprise_account_id: enterprise_account_id,
19
+ service: service,
18
20
  domain: enterprise_account.domain,
19
21
  )
20
22
 
21
- return_data(identity_provider: identity_provider)
22
- rescue StandardError => e
23
- return_error(errors: e.full_message)
23
+ return response_data(identity_provider: identity_provider) if identity_provider.save
24
+
25
+ response_error(errors: identity_provider.errors.full_messages)
24
26
  end
25
27
  end
26
28
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class CreateOauthClient < BaseMutation
7
+ null false
8
+
9
+ argument :name, String, required: true
10
+
11
+ field :oauth_client, Types::OauthClient, null: false
12
+ field :errors, [String], null: false
13
+
14
+ def resolve(**args)
15
+ oauth_client = Osso::Models::OauthClient.new(args)
16
+
17
+ return response_data(oauth_client: oauth_client) if oauth_client.save
18
+
19
+ response_error(errors: oauth_client.errors.full_messages)
20
+ end
21
+
22
+ def ready?(*)
23
+ return true if context[:scope] == :admin
24
+
25
+ raise ::GraphQL::ExecutionError, 'Only admin users may mutate OauthClients'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class DeleteEnterpriseAccount < BaseMutation
7
+ null false
8
+
9
+ argument :id, ID, required: true
10
+
11
+ field :enterprise_account, Types::EnterpriseAccount, null: true
12
+ field :errors, [String], null: false
13
+
14
+ def resolve(id:)
15
+ enterprise_account = Osso::Models::EnterpriseAccount.find(id)
16
+
17
+ return response_data(enterprise_account: nil) if enterprise_account.destroy
18
+
19
+ response_error(errors: enterprise_account.errors.full_messages)
20
+ end
21
+
22
+ def ready?(id:)
23
+ return true if context[:scope] == :admin
24
+
25
+ domain = account_domain(id)
26
+
27
+ return true if domain == context[:scope]
28
+
29
+ raise ::GraphQL::ExecutionError, "This user lacks the scope to mutate records belonging to #{domain}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class DeleteOauthClient < BaseMutation
7
+ null false
8
+
9
+ argument :id, ID, required: true
10
+
11
+ field :oauth_client, Types::OauthClient, null: true
12
+ field :errors, [String], null: false
13
+
14
+ def resolve(id:)
15
+ oauth_client = Osso::Models::OauthClient.find(id)
16
+
17
+ return response_data(oauth_client: nil) if oauth_client.destroy
18
+
19
+ response_error(errors: oauth_client.errors.full_messages)
20
+ end
21
+
22
+ def ready?(*)
23
+ return true if context[:scope] == :admin
24
+
25
+ raise ::GraphQL::ExecutionError, 'Only admin users may mutate OauthClients'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -7,7 +7,7 @@ module Osso
7
7
  field :enterprise_accounts, null: true, resolver: Resolvers::EnterpriseAccounts
8
8
  field :oauth_clients, null: true, resolver: Resolvers::OAuthClients
9
9
 
10
- field :enterprise_account, null: false, resolver: Resolvers::EnterpriseAccount do
10
+ field :enterprise_account, null: true, resolver: Resolvers::EnterpriseAccount do
11
11
  argument :domain, String, required: true
12
12
  end
13
13
 
@@ -15,7 +15,7 @@ module Osso
15
15
  :identity_provider,
16
16
  Types::IdentityProvider,
17
17
  null: true,
18
- resolve: ->(_obj, args, _context) { Osso::Models::SamlProvider.find(args[:id]) },
18
+ resolve: ->(_obj, args, _context) { Osso::Models::IdentityProvider.find(args[:id]) },
19
19
  ) do
20
20
  argument :id, ID, required: true
21
21
  end
@@ -4,10 +4,10 @@ module Osso
4
4
  module GraphQL
5
5
  module Resolvers
6
6
  class OAuthClients < ::GraphQL::Schema::Resolver
7
- type [Types::OAuthClient], null: true
7
+ type [Types::OauthClient], null: true
8
8
 
9
9
  def resolve
10
- return Osso::Models::OAuthClient.all if context[:scope] == :admin
10
+ return Osso::Models::OauthClient.all if context[:scope] == :admin
11
11
  end
12
12
  end
13
13
  end
@@ -31,12 +31,16 @@ module Osso
31
31
  case obj
32
32
  when Osso::Models::EnterpriseAccount
33
33
  Types::EnterpriseAccount
34
- when Osso::Models::SamlProvider
34
+ when Osso::Models::IdentityProvider
35
35
  Types::IdentityProvider
36
36
  else
37
37
  raise("Unexpected object: #{obj}")
38
38
  end
39
39
  end
40
+
41
+ def self.unauthorized_object(error)
42
+ raise ::GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions"
43
+ end
40
44
  end
41
45
  end
42
46
  end
@@ -7,7 +7,9 @@ end
7
7
 
8
8
  require_relative 'types/base_object'
9
9
  require_relative 'types/base_enum'
10
+ require_relative 'types/base_input_object'
10
11
  require_relative 'types/identity_provider_service'
12
+ require_relative 'types/identity_provider_status'
11
13
  require_relative 'types/identity_provider'
12
14
  require_relative 'types/enterprise_account'
13
15
  require_relative 'types/oauth_client'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class BaseInputObject < ::GraphQL::Schema::InputObject
7
+ end
8
+ end
9
+ end
10
+ end
@@ -6,6 +6,8 @@ module Osso
6
6
  module GraphQL
7
7
  module Types
8
8
  class BaseObject < ::GraphQL::Schema::Object
9
+ field :created_at, ::GraphQL::Types::ISO8601DateTime, null: false
10
+ field :updated_at, ::GraphQL::Types::ISO8601DateTime, null: false
9
11
  end
10
12
  end
11
13
  end
@@ -16,16 +16,16 @@ module Osso
16
16
  field :identity_providers, [Types::IdentityProvider], null: true
17
17
  field :status, String, null: false
18
18
 
19
- def name
20
- object.domain.gsub('.com', '')
21
- end
22
-
23
19
  def status
24
20
  'active'
25
21
  end
26
22
 
27
23
  def identity_providers
28
- object.saml_providers
24
+ object.identity_providers
25
+ end
26
+
27
+ def self.authorized?(object, context)
28
+ super && (context[:scope] == :admin || object.domain == context[:scope])
29
29
  end
30
30
  end
31
31
  end
@@ -17,22 +17,15 @@ module Osso
17
17
  field :acs_url, String, null: false
18
18
  field :sso_url, String, null: true
19
19
  field :sso_cert, String, null: true
20
- field :configured, Boolean, null: false
20
+ field :status, Types::IdentityProviderStatus, null: false
21
+ field :documentation_pdf_url, String, null: true
21
22
 
22
- def service
23
- @object.provider
23
+ def documentation_pdf_url
24
+ ENV['BASE_URL'] + '/identity_provider/documentation/' + @object.id
24
25
  end
25
26
 
26
- def configured
27
- @object.idp_sso_target_url && @object.idp_cert
28
- end
29
-
30
- def sso_cert
31
- @object.idp_cert
32
- end
33
-
34
- def sso_url
35
- @object.idp_sso_target_url
27
+ def self.authorized?(object, context)
28
+ super && (context[:scope] == :admin || object.domain == context[:scope])
36
29
  end
37
30
  end
38
31
  end
@@ -9,4 +9,4 @@ module Osso
9
9
  end
10
10
  end
11
11
  end
12
- end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class IdentityProviderStatus < BaseEnum
7
+ value('Pending', value: 'PENDING')
8
+ value('Configured', value: 'CONFIGURED')
9
+ value('Active', value: 'ACTIVE')
10
+ value('Error', value: 'ERROR')
11
+ end
12
+ end
13
+ end
14
+ end
@@ -5,7 +5,7 @@ require 'graphql'
5
5
  module Osso
6
6
  module GraphQL
7
7
  module Types
8
- class OAuthClient < Types::BaseObject
8
+ class OauthClient < Types::BaseObject
9
9
  description 'An OAuth client used to consume Osso SAML users'
10
10
  implements ::GraphQL::Types::Relay::Node
11
11
 
@@ -14,6 +14,18 @@ module Osso
14
14
  field :name, String, null: false
15
15
  field :client_id, String, null: false
16
16
  field :client_secret, String, null: false
17
+
18
+ def client_id
19
+ object.identifier
20
+ end
21
+
22
+ def client_secret
23
+ object.secret
24
+ end
25
+
26
+ def self.authorized?(object, context)
27
+ super && context[:scope] == :admin
28
+ end
17
29
  end
18
30
  end
19
31
  end
@@ -4,21 +4,18 @@ 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)
16
- payload, _args = JWT.decode(
17
- token,
18
- ENV['JWT_HMAC_SECRET'],
19
- true,
20
- { algorithm: 'HS256' },
21
- )
17
+ def enterprise_authorized?(_domain)
18
+ payload, _args = decode(token)
22
19
 
23
20
  @current_scope = payload['scope']
24
21
 
@@ -34,12 +31,7 @@ module Osso
34
31
  end
35
32
 
36
33
  def admin_authorized?
37
- payload, _args = JWT.decode(
38
- token,
39
- ENV['JWT_HMAC_SECRET'],
40
- true,
41
- { algorithm: 'HS256' },
42
- )
34
+ payload, _args = decode(token)
43
35
 
44
36
  if payload['scope'] == 'admin'
45
37
  @current_scope = :admin
@@ -64,6 +56,15 @@ module Osso
64
56
 
65
57
  redirect request.path
66
58
  end
59
+
60
+ def decode(token)
61
+ JWT.decode(
62
+ token,
63
+ ENV['JWT_HMAC_SECRET'],
64
+ true,
65
+ { algorithm: 'HS256' },
66
+ )
67
+ end
67
68
  end
68
69
  end
69
- end
70
+ 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
@@ -27,3 +27,21 @@ module Osso
27
27
  end
28
28
  end
29
29
  end
30
+
31
+ # == Schema Information
32
+ #
33
+ # Table name: access_tokens
34
+ #
35
+ # id :uuid not null, primary key
36
+ # token :string
37
+ # expires_at :datetime
38
+ # created_at :datetime not null
39
+ # updated_at :datetime not null
40
+ # user_id :uuid
41
+ # oauth_client_id :uuid
42
+ #
43
+ # Indexes
44
+ #
45
+ # index_access_tokens_on_oauth_client_id (oauth_client_id)
46
+ # index_access_tokens_on_user_id (user_id)
47
+ #