osso 0.0.3.14 → 0.0.3.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +17 -1
  3. data/.rubocop.yml +1 -0
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +10 -2
  6. data/README.md +3 -2
  7. data/bin/publish +18 -0
  8. data/db/schema.rb +9 -1
  9. data/lib/osso/db/migrate/20200826201852_create_app_config.rb +11 -0
  10. data/lib/osso/graphql/mutation.rb +7 -0
  11. data/lib/osso/graphql/mutations.rb +2 -0
  12. data/lib/osso/graphql/mutations/base_mutation.rb +18 -5
  13. data/lib/osso/graphql/mutations/configure_identity_provider.rb +8 -10
  14. data/lib/osso/graphql/mutations/create_enterprise_account.rb +7 -0
  15. data/lib/osso/graphql/mutations/create_identity_provider.rb +14 -5
  16. data/lib/osso/graphql/mutations/create_oauth_client.rb +1 -3
  17. data/lib/osso/graphql/mutations/delete_enterprise_account.rb +9 -11
  18. data/lib/osso/graphql/mutations/delete_oauth_client.rb +1 -3
  19. data/lib/osso/graphql/mutations/regenerate_oauth_credentials.rb +1 -3
  20. data/lib/osso/graphql/mutations/set_redirect_uris.rb +2 -4
  21. data/lib/osso/graphql/mutations/update_app_config.rb +30 -0
  22. data/lib/osso/graphql/query.rb +14 -0
  23. data/lib/osso/graphql/resolvers.rb +1 -0
  24. data/lib/osso/graphql/resolvers/base_resolver.rb +21 -0
  25. data/lib/osso/graphql/resolvers/enterprise_account.rb +1 -11
  26. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +2 -2
  27. data/lib/osso/graphql/resolvers/oauth_clients.rb +2 -2
  28. data/lib/osso/graphql/types.rb +2 -1
  29. data/lib/osso/graphql/types/admin_user.rb +22 -0
  30. data/lib/osso/graphql/types/app_config.rb +22 -0
  31. data/lib/osso/graphql/types/base_object.rb +22 -0
  32. data/lib/osso/graphql/types/enterprise_account.rb +0 -5
  33. data/lib/osso/graphql/types/identity_provider.rb +0 -6
  34. data/lib/osso/graphql/types/oauth_client.rb +2 -4
  35. data/lib/osso/graphql/types/redirect_uri.rb +2 -4
  36. data/lib/osso/helpers/auth.rb +40 -18
  37. data/lib/osso/lib/route_map.rb +2 -2
  38. data/lib/osso/models/app_config.rb +33 -0
  39. data/lib/osso/models/identity_provider.rb +6 -12
  40. data/lib/osso/models/models.rb +1 -0
  41. data/lib/osso/models/oauth_client.rb +1 -0
  42. data/lib/osso/models/redirect_uri.rb +0 -11
  43. data/lib/osso/routes/admin.rb +2 -2
  44. data/lib/osso/routes/auth.rb +29 -12
  45. data/lib/osso/routes/oauth.rb +25 -18
  46. data/lib/osso/version.rb +1 -1
  47. data/lib/tasks/bootstrap.rake +2 -0
  48. data/spec/graphql/mutations/configure_identity_provider_spec.rb +17 -4
  49. data/spec/graphql/mutations/create_enterprise_account_spec.rb +53 -4
  50. data/spec/graphql/mutations/create_identity_provider_spec.rb +18 -6
  51. data/spec/graphql/mutations/create_oauth_client_spec.rb +10 -3
  52. data/spec/graphql/mutations/delete_enterprise_account_spec.rb +18 -4
  53. data/spec/graphql/mutations/delete_oauth_client_spec.rb +8 -4
  54. data/spec/graphql/query/enterprise_account_spec.rb +21 -6
  55. data/spec/graphql/query/enterprise_accounts_spec.rb +4 -2
  56. data/spec/graphql/query/identity_provider_spec.rb +16 -6
  57. data/spec/graphql/query/oauth_clients_spec.rb +10 -7
  58. data/spec/helpers/auth_spec.rb +97 -0
  59. data/spec/models/identity_provider_spec.rb +12 -0
  60. data/spec/routes/auth_spec.rb +18 -0
  61. data/spec/routes/oauth_spec.rb +5 -2
  62. data/spec/spec_helper.rb +3 -0
  63. data/spec/support/views/error.erb +0 -0
  64. metadata +15 -6
  65. data/lib/osso/graphql/types/user.rb +0 -17
@@ -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,8 @@ 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'
13
+ require_relative 'types/app_config'
12
14
  require_relative 'types/identity_provider_service'
13
15
  require_relative 'types/identity_provider_status'
14
16
  require_relative 'types/identity_provider'
@@ -16,4 +18,3 @@ require_relative 'types/enterprise_account'
16
18
  require_relative 'types/redirect_uri'
17
19
  require_relative 'types/redirect_uri_input'
18
20
  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
@@ -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 AppConfig < Types::BaseObject
9
+ description 'Configuration values for your application'
10
+
11
+ field :id, ID, null: false
12
+ field :name, String, null: true
13
+ field :logo_url, String, null: true
14
+ field :contact_email, String, null: true
15
+
16
+ def self.authorized?(_object, context)
17
+ admin_authorized?(context)
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
@@ -3,10 +3,21 @@
3
3
  module Osso
4
4
  module Helpers
5
5
  module Auth
6
- attr_accessor :current_scope
6
+ END_USER_SCOPE = 'end-user'
7
+ INTERNAL_SCOPE = 'internal'
8
+ ADMIN_SCOPE = 'admin'
9
+
10
+ attr_accessor :current_user
11
+
12
+ def token_protected!
13
+ decode(token)
14
+ rescue JWT::DecodeError
15
+ halt 401
16
+ end
7
17
 
8
18
  def enterprise_protected!(domain = nil)
9
19
  return if admin_authorized?
20
+ return if internal_authorized?
10
21
  return if enterprise_authorized?(domain)
11
22
 
12
23
  halt 401 if request.post?
@@ -14,33 +25,42 @@ module Osso
14
25
  redirect ENV['JWT_URL']
15
26
  end
16
27
 
17
- # use client id in payload to restrict customer
18
- # users from accessing dev?
19
- def enterprise_authorized?(_domain)
20
- payload, _args = decode(token)
21
-
22
- @current_scope = payload['scope']
28
+ def internal_protected!
29
+ return if admin_authorized?
30
+ return if internal_authorized?
23
31
 
24
- true
25
- rescue JWT::DecodeError
26
- false
32
+ redirect ENV['JWT_URL']
27
33
  end
28
34
 
29
35
  def admin_protected!
30
- return if admin_authorized?
36
+ return true if admin_authorized?
31
37
 
32
38
  redirect ENV['JWT_URL']
33
39
  end
34
40
 
35
- def admin_authorized?
36
- payload, _args = decode(token)
41
+ private
37
42
 
38
- if payload['scope'] == 'admin'
39
- @current_scope = :admin
40
- return true
41
- end
43
+ def enterprise_authorized?(domain)
44
+ decode(token)
42
45
 
46
+ @current_user[:scope] == END_USER_SCOPE &&
47
+ @current_user[:email].split('@')[1] == domain
48
+ rescue JWT::DecodeError
43
49
  false
50
+ end
51
+
52
+ def internal_authorized?
53
+ decode(token)
54
+
55
+ @current_user[:scope] == INTERNAL_SCOPE
56
+ rescue JWT::DecodeError
57
+ false
58
+ end
59
+
60
+ def admin_authorized?
61
+ decode(token)
62
+
63
+ @current_user[:scope] == ADMIN_SCOPE
44
64
  rescue JWT::DecodeError
45
65
  false
46
66
  end
@@ -60,12 +80,14 @@ module Osso
60
80
  end
61
81
 
62
82
  def decode(token)
63
- JWT.decode(
83
+ payload, _args = JWT.decode(
64
84
  token,
65
85
  ENV['JWT_HMAC_SECRET'],
66
86
  true,
67
87
  { algorithm: 'HS256' },
68
88
  )
89
+
90
+ @current_user = payload.symbolize_keys
69
91
  end
70
92
  end
71
93
  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
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ class AppConfig < ::ActiveRecord::Base
6
+ validate :limit_to_one, on: :create
7
+
8
+ def self.find
9
+ first
10
+ end
11
+
12
+ private
13
+
14
+ def limit_to_one
15
+ return if Osso::Models::AppConfig.count.zero?
16
+
17
+ errors[:base] << 'AppConfig already exists'
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ # == Schema Information
24
+ #
25
+ # Table name: app_configs
26
+ #
27
+ # id :uuid not null, primary key
28
+ # contact_email :string
29
+ # logo_url :string
30
+ # name :string
31
+ # created_at :datetime not null
32
+ # updated_at :datetime not null
33
+ #
@@ -19,20 +19,14 @@ module Osso
19
19
  end
20
20
 
21
21
  def saml_options
22
- attributes.slice(
23
- 'domain',
24
- 'idp_cert',
25
- 'idp_sso_target_url',
26
- ).symbolize_keys
22
+ {
23
+ domain: domain,
24
+ idp_sso_target_url: sso_url,
25
+ idp_cert: sso_cert,
26
+ issuer: domain,
27
+ }
27
28
  end
28
29
 
29
- # def saml_options
30
- # raise(
31
- # NoMethodError,
32
- # '#saml_options must be defined on each provider specific subclass',
33
- # )
34
- # end
35
-
36
30
  def assertion_consumer_service_url
37
31
  [
38
32
  ENV.fetch('BASE_URL'),
@@ -10,6 +10,7 @@ module Osso
10
10
  end
11
11
 
12
12
  require_relative 'access_token'
13
+ require_relative 'app_config'
13
14
  require_relative 'authorization_code'
14
15
  require_relative 'enterprise_account'
15
16
  require_relative 'oauth_client'
@@ -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