osso 0.0.3.16 → 0.0.3.17

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 (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