osso 0.0.3.14 → 0.0.3.19

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