osso 0.0.3.16 → 0.0.3.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/Gemfile.lock +1 -1
  4. data/lib/osso/graphql/mutation.rb +6 -0
  5. data/lib/osso/graphql/mutations/base_mutation.rb +18 -5
  6. data/lib/osso/graphql/mutations/configure_identity_provider.rb +8 -10
  7. data/lib/osso/graphql/mutations/create_enterprise_account.rb +2 -0
  8. data/lib/osso/graphql/mutations/create_identity_provider.rb +14 -5
  9. data/lib/osso/graphql/mutations/create_oauth_client.rb +1 -3
  10. data/lib/osso/graphql/mutations/delete_enterprise_account.rb +9 -11
  11. data/lib/osso/graphql/mutations/delete_oauth_client.rb +1 -3
  12. data/lib/osso/graphql/mutations/regenerate_oauth_credentials.rb +1 -3
  13. data/lib/osso/graphql/mutations/set_redirect_uris.rb +1 -3
  14. data/lib/osso/graphql/query.rb +7 -0
  15. data/lib/osso/graphql/resolvers.rb +1 -0
  16. data/lib/osso/graphql/resolvers/base_resolver.rb +21 -0
  17. data/lib/osso/graphql/resolvers/enterprise_account.rb +1 -11
  18. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +2 -2
  19. data/lib/osso/graphql/resolvers/oauth_clients.rb +2 -2
  20. data/lib/osso/graphql/types.rb +1 -1
  21. data/lib/osso/graphql/types/admin_user.rb +22 -0
  22. data/lib/osso/graphql/types/base_object.rb +22 -0
  23. data/lib/osso/graphql/types/enterprise_account.rb +0 -5
  24. data/lib/osso/graphql/types/identity_provider.rb +0 -6
  25. data/lib/osso/graphql/types/oauth_client.rb +2 -4
  26. data/lib/osso/graphql/types/redirect_uri.rb +2 -4
  27. data/lib/osso/helpers/auth.rb +34 -15
  28. data/lib/osso/lib/route_map.rb +2 -2
  29. data/lib/osso/models/oauth_client.rb +1 -0
  30. data/lib/osso/routes/admin.rb +2 -2
  31. data/lib/osso/routes/auth.rb +7 -3
  32. data/lib/osso/routes/oauth.rb +21 -14
  33. data/lib/osso/version.rb +1 -1
  34. data/spec/graphql/mutations/configure_identity_provider_spec.rb +17 -4
  35. data/spec/graphql/mutations/create_enterprise_account_spec.rb +13 -4
  36. data/spec/graphql/mutations/create_identity_provider_spec.rb +18 -6
  37. data/spec/graphql/mutations/create_oauth_client_spec.rb +10 -3
  38. data/spec/graphql/mutations/delete_enterprise_account_spec.rb +18 -4
  39. data/spec/graphql/mutations/delete_oauth_client_spec.rb +8 -4
  40. data/spec/graphql/query/enterprise_account_spec.rb +21 -6
  41. data/spec/graphql/query/enterprise_accounts_spec.rb +4 -2
  42. data/spec/graphql/query/identity_provider_spec.rb +16 -6
  43. data/spec/graphql/query/oauth_clients_spec.rb +10 -7
  44. data/spec/routes/oauth_spec.rb +5 -2
  45. data/spec/support/views/error.erb +0 -0
  46. metadata +5 -3
  47. data/lib/osso/graphql/types/user.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e33fd333f7c329404b9a9bdeb62551629b38a8b615b6aef556bc4b4c0ca2a03
4
- data.tar.gz: e8c21ea78f2f33e5b6497c85148ff67221949ded6c34380dcb48a6eb450d6dc6
3
+ metadata.gz: b28fb9c155136c0d23356543f5d9ae0b15e551293bf18c4fbd44bd3340e6602e
4
+ data.tar.gz: f5c7495d581f4c27706a3fdeca7618707487308d6bc73d0f2372d4cf8fb1957d
5
5
  SHA512:
6
- metadata.gz: 76779ec670e1a6c12589a3b2a1f319e8855ba8770a83489787d56c43bb8a23aa9a75ee9b9864dfce76f54061e3829ce639214ba5f5a4c7c8efc699e1776801a6
7
- data.tar.gz: 356194bce279f215d58ea36e0e7e188c4ee134a4a4b55ce435fdee255caf23c39974f0b87a305e332a84b7aaf519db6ee13525beecd47117b9f9ebbd765679e3
6
+ metadata.gz: 67543b72337e89ebc7b7c2f80c42df2d8aa4f0c7001959022858f68e72fda5f409627260b614ba2fb1c1afbe51ecdded8449d84137cb2c85eff16225f9e7c387
7
+ data.tar.gz: 69fdf0abd7db72588068ec909c84bf95a19e7b93ace562ce86f24e278dc5b54cbb8c1b49f77a18d8f161542931610a9b9cd1e70cf9b0edb41b6397f49a7b4bbb
@@ -1,4 +1,5 @@
1
1
  AllCops:
2
+ TargetRubyVersion: 2.6.0
2
3
  Exclude:
3
4
  - db/**/*
4
5
  - lib/osso/db/**/*
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- osso (0.0.3.16)
4
+ osso (0.0.3.17)
5
5
  activesupport (>= 6.0.3.2)
6
6
  graphql
7
7
  jwt
@@ -14,6 +14,12 @@ module Osso
14
14
  field :delete_oauth_client, mutation: Mutations::DeleteOauthClient
15
15
  field :set_redirect_uris, mutation: Mutations::SetRedirectUris
16
16
  field :regenerate_oauth_credentials, mutation: Mutations::RegenerateOauthCredentials
17
+
18
+ def self.authorized?(_object, _context)
19
+ # mutations are prevented from executing with ready? so
20
+ # its a bit odd that this hides it
21
+ true
22
+ end
17
23
  end
18
24
  end
19
25
  end
@@ -15,13 +15,26 @@ module Osso
15
15
  error.merge(data: nil)
16
16
  end
17
17
 
18
- def ready?(enterprise_account_id: nil, domain: nil, identity_provider_id: nil, **args)
19
- return true if context[:scope] == :admin
18
+ def ready?(**args)
19
+ return true if internal_ready?
20
20
 
21
- domain ||= account_domain(enterprise_account_id) || provider_domain(identity_provider_id)
22
- return true if domain == context[:scope]
21
+ return true if domain_ready?(args[:domain] || domain(**args))
23
22
 
24
- raise ::GraphQL::ExecutionError, "This user lacks the scope to mutate records belonging to #{args[:domain]}"
23
+ raise ::GraphQL::ExecutionError, 'This user lacks the permission to make the requested changes'
24
+ end
25
+
26
+ def admin_ready?
27
+ context[:scope] == 'admin'
28
+ end
29
+
30
+ def internal_ready?
31
+ return true if admin_ready?
32
+
33
+ context[:scope] == 'internal'
34
+ end
35
+
36
+ def domain_ready?(domain)
37
+ context[:email].split('@')[1] == domain
25
38
  end
26
39
 
27
40
  def account_domain(id)
@@ -13,22 +13,20 @@ module Osso
13
13
  field :identity_provider, Types::IdentityProvider, null: false
14
14
  field :errors, [String], null: false
15
15
 
16
- def resolve(id:, **args)
17
- provider = Osso::Models::IdentityProvider.find(id)
16
+ def resolve(**args)
17
+ provider = identity_provider(**args)
18
18
 
19
19
  return response_data(identity_provider: provider) if provider.update(args)
20
20
 
21
- response_error(errors: provder.errors.messages)
21
+ response_error(errors: provider.errors.messages)
22
22
  end
23
23
 
24
- def ready?(id:, **_args)
25
- return true if context[:scope] == :admin
26
-
27
- domain = Osso::Models::IdentityProvider.find(id)&.domain
28
-
29
- return true if domain == context[:scope]
24
+ def domain(**args)
25
+ identity_provider(**args)&.domain
26
+ end
30
27
 
31
- raise ::GraphQL::ExecutionError, "This user lacks the scope to mutate records belonging to #{domain}"
28
+ def identity_provider(id:, **_args)
29
+ @identity_provider ||= Osso::Models::IdentityProvider.find(id)
32
30
  end
33
31
  end
34
32
  end
@@ -8,12 +8,14 @@ module Osso
8
8
 
9
9
  argument :domain, String, required: true
10
10
  argument :name, String, required: true
11
+ argument :oauth_client_id, ID, required: false
11
12
 
12
13
  field :enterprise_account, Types::EnterpriseAccount, null: false
13
14
  field :errors, [String], null: false
14
15
 
15
16
  def resolve(**args)
16
17
  enterprise_account = Osso::Models::EnterpriseAccount.new(args)
18
+ enterprise_account.oauth_client_id ||= context[:oauth_client_id]
17
19
 
18
20
  return response_data(enterprise_account: enterprise_account) if enterprise_account.save
19
21
 
@@ -12,18 +12,27 @@ module Osso
12
12
  field :identity_provider, Types::IdentityProvider, null: false
13
13
  field :errors, [String], null: false
14
14
 
15
- def resolve(enterprise_account_id:, service: nil)
16
- enterprise_account = Osso::Models::EnterpriseAccount.find(enterprise_account_id)
17
- identity_provider = enterprise_account.identity_providers.build(
18
- enterprise_account_id: enterprise_account_id,
15
+ def resolve(service: nil, **args)
16
+ customer = enterprise_account(**args)
17
+
18
+ identity_provider = customer.identity_providers.build(
19
19
  service: service,
20
- domain: enterprise_account.domain,
20
+ domain: customer.domain,
21
+ oauth_client_id: customer.oauth_client_id,
21
22
  )
22
23
 
23
24
  return response_data(identity_provider: identity_provider) if identity_provider.save
24
25
 
25
26
  response_error(errors: identity_provider.errors.full_messages)
26
27
  end
28
+
29
+ def domain(**args)
30
+ enterprise_account(**args)&.domain
31
+ end
32
+
33
+ def enterprise_account(enterprise_account_id:, **_args)
34
+ @enterprise_account ||= Osso::Models::EnterpriseAccount.find(enterprise_account_id)
35
+ end
27
36
  end
28
37
  end
29
38
  end
@@ -20,9 +20,7 @@ module Osso
20
20
  end
21
21
 
22
22
  def ready?(*)
23
- return true if context[:scope] == :admin
24
-
25
- raise ::GraphQL::ExecutionError, 'Only admin users may mutate OauthClients'
23
+ admin_ready?
26
24
  end
27
25
  end
28
26
  end
@@ -11,22 +11,20 @@ module Osso
11
11
  field :enterprise_account, Types::EnterpriseAccount, null: true
12
12
  field :errors, [String], null: false
13
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)
14
+ def enterprise_account(id:, **_args)
15
+ @enterprise_account ||= Osso::Models::EnterpriseAccount.find(id)
20
16
  end
21
17
 
22
- def ready?(id:)
23
- return true if context[:scope] == :admin
18
+ def resolve(**args)
19
+ customer = enterprise_account(**args)
24
20
 
25
- domain = account_domain(id)
21
+ return response_data(enterprise_account: nil) if customer.destroy
26
22
 
27
- return true if domain == context[:scope]
23
+ response_error(errors: customer.errors.full_messages)
24
+ end
28
25
 
29
- raise ::GraphQL::ExecutionError, "This user lacks the scope to mutate records belonging to #{domain}"
26
+ def domain(**args)
27
+ enterprise_account(**args).domain
30
28
  end
31
29
  end
32
30
  end
@@ -20,9 +20,7 @@ module Osso
20
20
  end
21
21
 
22
22
  def ready?(*)
23
- return true if context[:scope] == :admin
24
-
25
- raise ::GraphQL::ExecutionError, 'Only admin users may mutate OauthClients'
23
+ admin_ready?
26
24
  end
27
25
  end
28
26
  end
@@ -21,9 +21,7 @@ module Osso
21
21
  end
22
22
 
23
23
  def ready?(*)
24
- return true if context[:scope] == :admin
25
-
26
- raise ::GraphQL::ExecutionError, 'Only admin users may mutate OauthClients'
24
+ admin_ready?
27
25
  end
28
26
  end
29
27
  end
@@ -24,9 +24,7 @@ module Osso
24
24
  end
25
25
 
26
26
  def ready?(*)
27
- return true if context[:scope] == :admin
28
-
29
- raise ::GraphQL::ExecutionError, 'Only admin users may mutate OauthClients'
27
+ admin_ready?
30
28
  end
31
29
 
32
30
  def update_existing(oauth_client, redirect_uris)
@@ -32,6 +32,13 @@ module Osso
32
32
  ) do
33
33
  argument :id, ID, required: true
34
34
  end
35
+
36
+ field(
37
+ :current_user,
38
+ Types::AdminUser,
39
+ null: false,
40
+ resolve: ->(_obj, _args, context) { context.to_h },
41
+ )
35
42
  end
36
43
  end
37
44
  end
@@ -7,6 +7,7 @@ module Osso
7
7
  end
8
8
  end
9
9
 
10
+ require_relative 'resolvers/base_resolver'
10
11
  require_relative 'resolvers/enterprise_account'
11
12
  require_relative 'resolvers/enterprise_accounts'
12
13
  require_relative 'resolvers/oauth_clients'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Resolvers
6
+ class BaseResolver < ::GraphQL::Schema::Resolver
7
+ def admin_authorized?
8
+ context[:scope] == 'admin'
9
+ end
10
+
11
+ def internal_authorized?
12
+ %w[admin internal].include?(context[:scope])
13
+ end
14
+
15
+ def enterprise_authorized?(domain)
16
+ context[:scope] == domain
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -3,22 +3,12 @@
3
3
  module Osso
4
4
  module GraphQL
5
5
  module Resolvers
6
- class EnterpriseAccount < ::GraphQL::Schema::Resolver
6
+ class EnterpriseAccount < BaseResolver
7
7
  type Types::EnterpriseAccount, null: false
8
8
 
9
9
  def resolve(args)
10
- return unless admin? || enterprise_authorized?(args[:domain])
11
-
12
10
  Osso::Models::EnterpriseAccount.find_by(domain: args[:domain])
13
11
  end
14
-
15
- def admin?
16
- context[:scope] == :admin
17
- end
18
-
19
- def enterprise_authorized?(domain)
20
- context[:scope] == domain
21
- end
22
12
  end
23
13
  end
24
14
  end
@@ -3,11 +3,11 @@
3
3
  module Osso
4
4
  module GraphQL
5
5
  module Resolvers
6
- class EnterpriseAccounts < ::GraphQL::Schema::Resolver
6
+ class EnterpriseAccounts < BaseResolver
7
7
  type Types::EnterpriseAccount.connection_type, null: true
8
8
 
9
9
  def resolve(sort_column: nil, sort_order: nil)
10
- return Array(Osso::Models::EnterpriseAccount.find_by(domain: context[:scope])) if context[:scope] != :admin
10
+ return Array(Osso::Models::EnterpriseAccount.find_by(domain: context[:scope])) unless internal_authorized?
11
11
 
12
12
  accounts = Osso::Models::EnterpriseAccount
13
13
 
@@ -3,11 +3,11 @@
3
3
  module Osso
4
4
  module GraphQL
5
5
  module Resolvers
6
- class OAuthClients < ::GraphQL::Schema::Resolver
6
+ class OAuthClients < BaseResolver
7
7
  type [Types::OauthClient], null: true
8
8
 
9
9
  def resolve
10
- return Osso::Models::OauthClient.all if context[:scope] == :admin
10
+ Osso::Models::OauthClient.all
11
11
  end
12
12
  end
13
13
  end
@@ -9,6 +9,7 @@ require_relative 'types/base_connection'
9
9
  require_relative 'types/base_object'
10
10
  require_relative 'types/base_enum'
11
11
  require_relative 'types/base_input_object'
12
+ require_relative 'types/admin_user'
12
13
  require_relative 'types/identity_provider_service'
13
14
  require_relative 'types/identity_provider_status'
14
15
  require_relative 'types/identity_provider'
@@ -16,4 +17,3 @@ require_relative 'types/enterprise_account'
16
17
  require_relative 'types/redirect_uri'
17
18
  require_relative 'types/redirect_uri_input'
18
19
  require_relative 'types/oauth_client'
19
- require_relative 'types/user'
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class AdminUser < Types::BaseObject
9
+ description 'An Admin User of Osso'
10
+
11
+ field :id, ID, null: false
12
+ field :email, String, null: false
13
+ field :scope, String, null: false
14
+ field :oauth_client_id, ID, null: true
15
+
16
+ def self.authorized?(_object, _context)
17
+ true
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -10,6 +10,28 @@ module Osso
10
10
 
11
11
  field :created_at, ::GraphQL::Types::ISO8601DateTime, null: false
12
12
  field :updated_at, ::GraphQL::Types::ISO8601DateTime, null: false
13
+
14
+ def self.admin_authorized?(context)
15
+ context[:scope] == 'admin'
16
+ end
17
+
18
+ def self.internal_authorized?(context)
19
+ %w[admin internal].include?(context[:scope])
20
+ end
21
+
22
+ def self.enterprise_authorized?(context, domain)
23
+ return false unless domain
24
+
25
+ context[:email].split('@')[1] == domain
26
+ end
27
+
28
+ def self.authorized?(object, context)
29
+ # we first receive the payload object as a hash, but can depend on the
30
+ # return type to hide the actual objects non-admins shouldn't see
31
+ return true if object.class == Hash
32
+
33
+ internal_authorized?(context) || enterprise_authorized?(context, object&.domain)
34
+ end
13
35
  end
14
36
  end
15
37
  end
@@ -9,7 +9,6 @@ module Osso
9
9
  description 'An Account for a company that wishes to use SAML via Osso'
10
10
  implements ::GraphQL::Types::Relay::Node
11
11
 
12
- global_id_field :gid
13
12
  field :id, ID, null: false
14
13
  field :name, String, null: false
15
14
  field :domain, String, null: false
@@ -23,10 +22,6 @@ module Osso
23
22
  def identity_providers
24
23
  object.identity_providers
25
24
  end
26
-
27
- def self.authorized?(object, context)
28
- super && (context[:scope] == :admin || object.domain == context[:scope])
29
- end
30
25
  end
31
26
  end
32
27
  end
@@ -7,9 +7,7 @@ module Osso
7
7
  module Types
8
8
  class IdentityProvider < Types::BaseObject
9
9
  description 'Represents a SAML based IDP instance for an EnterpriseAccount'
10
- implements ::GraphQL::Types::Relay::Node
11
10
 
12
- global_id_field :gid
13
11
  field :id, ID, null: false
14
12
  field :enterprise_account_id, ID, null: false
15
13
  field :service, Types::IdentityProviderService, null: true
@@ -23,10 +21,6 @@ module Osso
23
21
  def documentation_pdf_url
24
22
  ENV['BASE_URL'] + '/identity_provider/documentation/' + @object.id
25
23
  end
26
-
27
- def self.authorized?(object, context)
28
- super && (context[:scope] == :admin || object.domain == context[:scope])
29
- end
30
24
  end
31
25
  end
32
26
  end
@@ -7,9 +7,7 @@ module Osso
7
7
  module Types
8
8
  class OauthClient < Types::BaseObject
9
9
  description 'An OAuth client used to consume Osso SAML users'
10
- implements ::GraphQL::Types::Relay::Node
11
10
 
12
- global_id_field :gid
13
11
  field :id, ID, null: false
14
12
  field :name, String, null: false
15
13
  field :client_id, String, null: false
@@ -24,8 +22,8 @@ module Osso
24
22
  object.secret
25
23
  end
26
24
 
27
- def self.authorized?(object, context)
28
- super && context[:scope] == :admin
25
+ def self.authorized?(_object, context)
26
+ admin_authorized?(context)
29
27
  end
30
28
  end
31
29
  end
@@ -7,15 +7,13 @@ module Osso
7
7
  module Types
8
8
  class RedirectUri < Types::BaseObject
9
9
  description 'An allowed redirect URI for an OauthClient'
10
- implements ::GraphQL::Types::Relay::Node
11
10
 
12
- global_id_field :gid
13
11
  field :id, ID, null: false
14
12
  field :uri, String, null: false
15
13
  field :primary, Boolean, null: false
16
14
 
17
- def self.authorized?(object, context)
18
- super && context[:scope] == :admin
15
+ def self.authorized?(_object, context)
16
+ context[:scope] == 'admin'
19
17
  end
20
18
  end
21
19
  end
@@ -1,12 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pry'
3
4
  module Osso
4
5
  module Helpers
5
6
  module Auth
6
- attr_accessor :current_scope
7
+ END_USER_SCOPE = 'end-user'
8
+ INTERNAL_SCOPE = 'internal'
9
+ ADMIN_SCOPE = 'admin'
10
+
11
+ attr_accessor :current_user
12
+
13
+ def token_protected!
14
+ decode(token)
15
+ end
7
16
 
8
17
  def enterprise_protected!(domain = nil)
9
18
  return if admin_authorized?
19
+ return if internal_authorized?
10
20
  return if enterprise_authorized?(domain)
11
21
 
12
22
  halt 401 if request.post?
@@ -14,14 +24,26 @@ module Osso
14
24
  redirect ENV['JWT_URL']
15
25
  end
16
26
 
17
- # use client id in payload to restrict customer
18
- # users from accessing dev?
19
- def enterprise_authorized?(_domain)
20
- payload, _args = decode(token)
27
+ def enterprise_authorized?(domain)
28
+ decode(token)
29
+
30
+ @current_user[:scope] == END_USER_SCOPE &&
31
+ @current_user[:email].split('@')[1] == domain
32
+ rescue JWT::DecodeError
33
+ false
34
+ end
21
35
 
22
- @current_scope = payload['scope']
36
+ def internal_protected!
37
+ return if admin_authorized?
38
+ return if internal_authorized?
23
39
 
24
- true
40
+ redirect ENV['JWT_URL']
41
+ end
42
+
43
+ def internal_authorized?
44
+ decode(token)
45
+
46
+ @current_user[:scope] == INTERNAL_SCOPE
25
47
  rescue JWT::DecodeError
26
48
  false
27
49
  end
@@ -33,14 +55,9 @@ module Osso
33
55
  end
34
56
 
35
57
  def admin_authorized?
36
- payload, _args = decode(token)
58
+ decode(token)
37
59
 
38
- if payload['scope'] == 'admin'
39
- @current_scope = :admin
40
- return true
41
- end
42
-
43
- false
60
+ @current_user[:scope] == ADMIN_SCOPE
44
61
  rescue JWT::DecodeError
45
62
  false
46
63
  end
@@ -60,12 +77,14 @@ module Osso
60
77
  end
61
78
 
62
79
  def decode(token)
63
- JWT.decode(
80
+ payload, _args = JWT.decode(
64
81
  token,
65
82
  ENV['JWT_HMAC_SECRET'],
66
83
  true,
67
84
  { algorithm: 'HS256' },
68
85
  )
86
+
87
+ @current_user = payload.symbolize_keys
69
88
  end
70
89
  end
71
90
  end
@@ -11,12 +11,12 @@ module Osso
11
11
  use Osso::Oauth
12
12
 
13
13
  post '/graphql' do
14
- enterprise_protected!
14
+ token_protected!
15
15
 
16
16
  result = Osso::GraphQL::Schema.execute(
17
17
  params[:query],
18
18
  variables: params[:variables],
19
- context: { scope: current_scope },
19
+ context: current_user.symbolize_keys,
20
20
  )
21
21
 
22
22
  json result
@@ -5,6 +5,7 @@ module Osso
5
5
  module Models
6
6
  class OauthClient < ActiveRecord::Base
7
7
  has_many :access_tokens
8
+ has_many :enterprise_accounts
8
9
  has_many :refresh_tokens
9
10
  has_many :identity_providers
10
11
  has_many :redirect_uris
@@ -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
@@ -27,7 +27,11 @@ module Osso
27
27
  end
28
28
  end
29
29
 
30
- namespace '/auth' do
30
+ namespace '/auth' do # rubocop:disable Metrics/BlockLength
31
+ get '/failure' do
32
+ @error = params[:message]
33
+ erb :error
34
+ end
31
35
  # Enterprise users are sent here after authenticating against
32
36
  # their Identity Provider. We find or create a user record,
33
37
  # and then create an authorization code for that user. The user
@@ -67,9 +71,9 @@ module Osso
67
71
  end
68
72
 
69
73
  def provider_state
70
- return 'IDP_INITIATED' if valid_idp_initiated_flow
74
+ return @provider_state = 'IDP_INITIATED' if valid_idp_initiated_flow
71
75
 
72
- session[:osso_oauth_state]
76
+ session.delete(:osso_oauth_state)
73
77
  end
74
78
 
75
79
  def valid_idp_initiated_flow
@@ -7,37 +7,43 @@ module Osso
7
7
  include AppConfig
8
8
  register Sinatra::Namespace
9
9
 
10
- namespace '/oauth' do
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
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[:osso_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
44
  # Exchange an authorization code for an access token.
39
- # In addition to the authorization code, you must include all
40
- # paramaters required by OAuth spec: redirect_uri, client ID,
45
+ # In addition to the authorization code, you must include all
46
+ # paramaters required by OAuth spec: redirect_uri, client ID,
41
47
  # and client secret
42
48
  post '/token' do
43
49
  Rack::OAuth2::Server::Token.new do |req, res|
@@ -50,7 +56,8 @@ module Osso
50
56
  end.call(env)
51
57
  end
52
58
 
53
- # 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.
54
61
  get '/me' do
55
62
  json Models::AccessToken.
56
63
  includes(:user).
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Osso
4
- VERSION = '0.0.3.16'
4
+ VERSION = '0.0.3.17'
5
5
  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
@@ -33,12 +33,14 @@ describe Osso::GraphQL::Schema do
33
33
  described_class.execute(
34
34
  mutation,
35
35
  variables: variables,
36
- context: { scope: current_scope },
36
+ context: current_context,
37
37
  )
38
38
  end
39
39
 
40
40
  describe 'for an admin user' do
41
- let(:current_scope) { :admin }
41
+ let(:current_context) do
42
+ { scope: 'admin' }
43
+ end
42
44
  it 'creates an Enterprise Account' do
43
45
  expect { subject }.to change { Osso::Models::EnterpriseAccount.count }.by(1)
44
46
  expect(subject.dig('data', 'createEnterpriseAccount', 'enterpriseAccount', 'domain')).
@@ -47,7 +49,12 @@ describe Osso::GraphQL::Schema do
47
49
  end
48
50
 
49
51
  describe 'for an email scoped user' do
50
- let(:current_scope) { domain }
52
+ let(:current_context) do
53
+ {
54
+ scope: 'end-user',
55
+ email: "user@#{domain}",
56
+ }
57
+ end
51
58
 
52
59
  it 'creates an Enterprise Account' do
53
60
  expect { subject }.to change { Osso::Models::EnterpriseAccount.count }.by(1)
@@ -56,7 +63,9 @@ describe Osso::GraphQL::Schema do
56
63
  end
57
64
  end
58
65
  describe 'for the wrong email scoped user' do
59
- let(:current_scope) { 'foo.com' }
66
+ let(:current_context) do
67
+ { scope: 'end-user', email: 'user@foo.com' }
68
+ end
60
69
 
61
70
  it 'does not create an Enterprise Account' do
62
71
  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 })
@@ -29,21 +29,25 @@ describe Osso::GraphQL::Schema do
29
29
  described_class.execute(
30
30
  mutation,
31
31
  variables: variables,
32
- context: { scope: current_scope },
32
+ context: current_context,
33
33
  )
34
34
  end
35
35
 
36
36
  describe 'for an admin user' do
37
- let(:current_scope) { :admin }
37
+ let(:current_context) do
38
+ { scope: 'admin' }
39
+ end
38
40
  it 'deletes the OauthClient' do
39
41
  expect { subject }.to change { Osso::Models::OauthClient.count }.by(-1)
40
42
  end
41
43
  end
42
44
 
43
45
  describe 'for an email scoped user' do
44
- let(:current_scope) { 'foo.com' }
46
+ let(:current_context) do
47
+ { scope: 'end-user', email: 'user@foo.com' }
48
+ end
45
49
 
46
- it 'does not create an OauthClient Account' do
50
+ it 'does not deletes the OauthClient' do
47
51
  expect { subject }.to_not(change { Osso::Models::OauthClient.count })
48
52
  end
49
53
  end
@@ -37,12 +37,17 @@ describe Osso::GraphQL::Schema do
37
37
  described_class.execute(
38
38
  query,
39
39
  variables: variables,
40
- context: { scope: current_scope },
40
+ context: current_context,
41
41
  )
42
42
  end
43
43
 
44
44
  describe 'for an admin user' do
45
- let(:current_scope) { :admin }
45
+ let(:current_context) do
46
+ {
47
+ scope: 'admin',
48
+ }
49
+ end
50
+
46
51
  it 'returns Enterprise Account for domain' do
47
52
  expect(subject['errors']).to be_nil
48
53
  expect(subject.dig('data', 'enterpriseAccount', 'domain')).to eq(domain)
@@ -50,7 +55,12 @@ describe Osso::GraphQL::Schema do
50
55
  end
51
56
 
52
57
  describe 'for an email scoped user' do
53
- let(:current_scope) { domain }
58
+ let(:current_context) do
59
+ {
60
+ scope: 'end-user',
61
+ email: "user@#{domain}",
62
+ }
63
+ end
54
64
  it 'returns Enterprise Account for domain' do
55
65
  expect(subject['errors']).to be_nil
56
66
  expect(subject.dig('data', 'enterpriseAccount', 'domain')).to eq(domain)
@@ -58,9 +68,14 @@ describe Osso::GraphQL::Schema do
58
68
  end
59
69
 
60
70
  describe 'for the wrong email scoped user' do
61
- let(:current_scope) { 'bar.com' }
62
- it 'returns Enterprise Account for domain' do
63
- expect(subject['errors']).to be_nil
71
+ let(:current_context) do
72
+ {
73
+ scope: 'end-user',
74
+ email: 'foo@bar.com',
75
+ }
76
+ end
77
+ it 'does not return Enterprise Account for domain' do
78
+ expect(subject['errors']).to_not be_nil
64
79
  expect(subject.dig('data', 'enterpriseAccount')).to be_nil
65
80
  end
66
81
  end
@@ -5,7 +5,9 @@ require 'spec_helper'
5
5
  describe Osso::GraphQL::Schema do
6
6
  describe 'EnterpriseAccounts' do
7
7
  describe 'for an admin user' do
8
- let(:current_scope) { :admin }
8
+ let(:current_context) do
9
+ { scope: 'admin' }
10
+ end
9
11
 
10
12
  it 'returns paginated Enterprise Accounts' do
11
13
  %w[A B C].map do |name|
@@ -44,7 +46,7 @@ describe Osso::GraphQL::Schema do
44
46
  response = described_class.execute(
45
47
  query,
46
48
  variables: { first: 2, sortOrder: 'descending', sortColumn: 'name' },
47
- context: { scope: current_scope },
49
+ context: current_context,
48
50
  )
49
51
 
50
52
  expect(response['errors']).to be_nil
@@ -32,12 +32,14 @@ describe Osso::GraphQL::Schema do
32
32
  described_class.execute(
33
33
  query,
34
34
  variables: variables,
35
- context: { scope: current_scope },
35
+ context: current_context,
36
36
  )
37
37
  end
38
38
 
39
39
  describe 'for an admin user' do
40
- let(:current_scope) { :admin }
40
+ let(:current_context) do
41
+ { scope: 'admin' }
42
+ end
41
43
  it 'returns Identity Provider for id' do
42
44
  expect(subject['errors']).to be_nil
43
45
  expect(subject.dig('data', 'identityProvider', 'id')).to eq(id)
@@ -45,8 +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) { domain }
49
-
50
+ let(:current_context) do
51
+ {
52
+ scope: 'end-user',
53
+ email: "user@#{domain}",
54
+ }
55
+ end
50
56
  it 'returns Enterprise Account for domain' do
51
57
  expect(subject['errors']).to be_nil
52
58
  expect(subject.dig('data', 'identityProvider', 'domain')).to eq(domain)
@@ -54,8 +60,12 @@ describe Osso::GraphQL::Schema do
54
60
  end
55
61
 
56
62
  describe 'for the wrong email scoped user' do
57
- let(:current_scope) { 'bar.com' }
58
-
63
+ let(:current_context) do
64
+ {
65
+ scope: 'end-user',
66
+ email: 'user@bar.com',
67
+ }
68
+ end
59
69
  it 'returns Enterprise Account for domain' do
60
70
  expect(subject['errors']).to_not be_empty
61
71
  expect(subject.dig('data', 'enterpriseAccount')).to be_nil
@@ -25,12 +25,14 @@ describe Osso::GraphQL::Schema do
25
25
  described_class.execute(
26
26
  query,
27
27
  variables: nil,
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
 
35
37
  it 'returns Oauth Clients' do
36
38
  expect(subject['errors']).to be_nil
@@ -38,11 +40,12 @@ describe Osso::GraphQL::Schema do
38
40
  end
39
41
  end
40
42
 
41
- describe 'for an email scoped user' do
42
- let(:current_scope) { 'foo.com' }
43
-
44
- it 'returns Oauth Clients' do
45
- expect(subject['errors']).to be_nil
43
+ describe 'for an internal scoped user' do
44
+ let(:current_context) do
45
+ { scope: 'internal' }
46
+ end
47
+ it 'does not return Oauth Clients' do
48
+ expect(subject['errors']).to_not be_nil
46
49
  expect(subject.dig('data', 'oauthClients')).to be_nil
47
50
  end
48
51
  end
@@ -8,7 +8,10 @@ describe Osso::Oauth do
8
8
  describe 'get /oauth/authorize' do
9
9
  describe 'with a valid client ID and redirect URI' do
10
10
  describe 'for a domain that does not belong to an enterprise' do
11
- it '404s' do
11
+ # TODO: better error handling and test
12
+ it 'renders an error page' do
13
+ described_class.set(:views, spec_views)
14
+
12
15
  create(:enterprise_with_okta, domain: 'foo.com')
13
16
 
14
17
  get(
@@ -19,7 +22,7 @@ describe Osso::Oauth do
19
22
  redirect_uri: client.redirect_uri_values.sample,
20
23
  )
21
24
 
22
- expect(last_response.status).to eq(404)
25
+ expect(last_response.status).to eq(200)
23
26
  end
24
27
  end
25
28
 
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3.16
4
+ version: 0.0.3.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Bauch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-17 00:00:00.000000000 Z
11
+ date: 2020-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -284,11 +284,13 @@ files:
284
284
  - lib/osso/graphql/mutations/set_redirect_uris.rb
285
285
  - lib/osso/graphql/query.rb
286
286
  - lib/osso/graphql/resolvers.rb
287
+ - lib/osso/graphql/resolvers/base_resolver.rb
287
288
  - lib/osso/graphql/resolvers/enterprise_account.rb
288
289
  - lib/osso/graphql/resolvers/enterprise_accounts.rb
289
290
  - lib/osso/graphql/resolvers/oauth_clients.rb
290
291
  - lib/osso/graphql/schema.rb
291
292
  - lib/osso/graphql/types.rb
293
+ - lib/osso/graphql/types/admin_user.rb
292
294
  - lib/osso/graphql/types/base_connection.rb
293
295
  - lib/osso/graphql/types/base_enum.rb
294
296
  - lib/osso/graphql/types/base_input_object.rb
@@ -300,7 +302,6 @@ files:
300
302
  - lib/osso/graphql/types/oauth_client.rb
301
303
  - lib/osso/graphql/types/redirect_uri.rb
302
304
  - lib/osso/graphql/types/redirect_uri_input.rb
303
- - lib/osso/graphql/types/user.rb
304
305
  - lib/osso/helpers/auth.rb
305
306
  - lib/osso/helpers/helpers.rb
306
307
  - lib/osso/lib/app_config.rb
@@ -350,6 +351,7 @@ files:
350
351
  - spec/spec_helper.rb
351
352
  - spec/support/spec_app.rb
352
353
  - spec/support/views/admin.erb
354
+ - spec/support/views/error.erb
353
355
  homepage: https://github.com/enterprise-oss/osso-rb
354
356
  licenses:
355
357
  - MIT
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'graphql'
4
- require_relative 'base_object'
5
-
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
16
- end
17
- end