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
@@ -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
+ #