osso 0.0.3.7

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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/.buildkite/hooks/environment +9 -0
  3. data/.buildkite/hooks/pre-command +7 -0
  4. data/.buildkite/pipeline.yml +6 -0
  5. data/.buildkite/template.yml +5 -0
  6. data/.gitignore +10 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +81 -0
  9. data/CODE_OF_CONDUCT.md +130 -0
  10. data/Gemfile +18 -0
  11. data/Gemfile.lock +176 -0
  12. data/LICENSE +111 -0
  13. data/README.md +2 -0
  14. data/Rakefile +14 -0
  15. data/bin/console +8 -0
  16. data/bin/setup +8 -0
  17. data/config/database.yml +14 -0
  18. data/db/schema.rb +133 -0
  19. data/lib/osso.rb +11 -0
  20. data/lib/osso/Rakefile +13 -0
  21. data/lib/osso/db/migrate/20190909230109_enable_uuid.rb +7 -0
  22. data/lib/osso/db/migrate/20200328135750_create_users.rb +12 -0
  23. data/lib/osso/db/migrate/20200328143303_create_oauth_tables.rb +57 -0
  24. data/lib/osso/db/migrate/20200328143305_create_identity_providers.rb +12 -0
  25. data/lib/osso/db/migrate/20200411184535_add_provider_id_to_users.rb +7 -0
  26. data/lib/osso/db/migrate/20200411192645_create_enterprise_accounts.rb +15 -0
  27. data/lib/osso/db/migrate/20200413132407_add_oauth_clients.rb +13 -0
  28. data/lib/osso/db/migrate/20200413142511_create_authorization_codes.rb +15 -0
  29. data/lib/osso/db/migrate/20200413163451_create_access_tokens.rb +13 -0
  30. data/lib/osso/db/migrate/20200502120616_create_redirect_uris_and_drop_from_oauth_clients.rb +13 -0
  31. data/lib/osso/db/migrate/20200502135008_add_oauth_client_id_to_enterprise_accounts_and_identity_providers.rb +6 -0
  32. data/lib/osso/db/migrate/20200714223226_add_identity_provider_service_enum.rb +17 -0
  33. data/lib/osso/db/migrate/20200715154211_rename_idp_fields_on_identity_provider_to_sso.rb +6 -0
  34. data/lib/osso/db/migrate/20200715205801_add_name_to_enterprise_account.rb +5 -0
  35. data/lib/osso/graphql/mutation.rb +16 -0
  36. data/lib/osso/graphql/mutations.rb +12 -0
  37. data/lib/osso/graphql/mutations/base_mutation.rb +41 -0
  38. data/lib/osso/graphql/mutations/configure_identity_provider.rb +36 -0
  39. data/lib/osso/graphql/mutations/create_enterprise_account.rb +25 -0
  40. data/lib/osso/graphql/mutations/create_identity_provider.rb +30 -0
  41. data/lib/osso/graphql/mutations/set_identity_provider.rb +27 -0
  42. data/lib/osso/graphql/query.rb +25 -0
  43. data/lib/osso/graphql/resolvers.rb +12 -0
  44. data/lib/osso/graphql/resolvers/enterprise_account.rb +25 -0
  45. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +17 -0
  46. data/lib/osso/graphql/resolvers/oauth_clients.rb +15 -0
  47. data/lib/osso/graphql/schema.rb +46 -0
  48. data/lib/osso/graphql/types.rb +15 -0
  49. data/lib/osso/graphql/types/base_enum.rb +10 -0
  50. data/lib/osso/graphql/types/base_input_object.rb +10 -0
  51. data/lib/osso/graphql/types/base_object.rb +12 -0
  52. data/lib/osso/graphql/types/enterprise_account.rb +33 -0
  53. data/lib/osso/graphql/types/identity_provider.rb +37 -0
  54. data/lib/osso/graphql/types/identity_provider_service.rb +12 -0
  55. data/lib/osso/graphql/types/oauth_client.rb +20 -0
  56. data/lib/osso/graphql/types/user.rb +17 -0
  57. data/lib/osso/helpers/auth.rb +71 -0
  58. data/lib/osso/helpers/helpers.rb +8 -0
  59. data/lib/osso/lib/app_config.rb +20 -0
  60. data/lib/osso/lib/oauth2_token.rb +38 -0
  61. data/lib/osso/lib/route_map.rb +28 -0
  62. data/lib/osso/models/access_token.rb +29 -0
  63. data/lib/osso/models/authorization_code.rb +14 -0
  64. data/lib/osso/models/enterprise_account.rb +28 -0
  65. data/lib/osso/models/identity_provider.rb +48 -0
  66. data/lib/osso/models/models.rb +16 -0
  67. data/lib/osso/models/oauth_client.rb +32 -0
  68. data/lib/osso/models/redirect_uri.rb +20 -0
  69. data/lib/osso/models/saml_provider.rb +49 -0
  70. data/lib/osso/models/saml_providers/azure_saml_provider.rb +22 -0
  71. data/lib/osso/models/saml_providers/okta_saml_provider.rb +23 -0
  72. data/lib/osso/models/user.rb +24 -0
  73. data/lib/osso/rake.rb +4 -0
  74. data/lib/osso/routes/admin.rb +41 -0
  75. data/lib/osso/routes/auth.rb +67 -0
  76. data/lib/osso/routes/oauth.rb +63 -0
  77. data/lib/osso/routes/routes.rb +10 -0
  78. data/lib/osso/routes/views/error.erb +1 -0
  79. data/lib/osso/routes/views/multiple_providers.erb +1 -0
  80. data/lib/osso/version.rb +5 -0
  81. data/lib/tasks/bootstrap.rake +16 -0
  82. data/osso-rb.gemspec +40 -0
  83. data/spec/factories/authorization_code.rb +10 -0
  84. data/spec/factories/enterprise_account.rb +46 -0
  85. data/spec/factories/identity_providers.rb +49 -0
  86. data/spec/factories/oauth_client.rb +12 -0
  87. data/spec/factories/redirect_uri.rb +14 -0
  88. data/spec/factories/user.rb +18 -0
  89. data/spec/graphql/mutations/configure_identity_provider_spec.rb +75 -0
  90. data/spec/graphql/mutations/create_enterprise_account_spec.rb +68 -0
  91. data/spec/graphql/mutations/create_identity_provider_spec.rb +104 -0
  92. data/spec/graphql/query/enterprise_account_spec.rb +68 -0
  93. data/spec/graphql/query/enterprise_accounts_spec.rb +44 -0
  94. data/spec/graphql/query/identity_provider_spec.rb +65 -0
  95. data/spec/graphql/query/oauth_clients_account_spec.rb +48 -0
  96. data/spec/models/azure_saml_provider_spec.rb +19 -0
  97. data/spec/models/identity_provider_spec.rb +17 -0
  98. data/spec/models/okta_saml_provider_spec.rb +20 -0
  99. data/spec/routes/admin_spec.rb +60 -0
  100. data/spec/routes/app_spec.rb +6 -0
  101. data/spec/routes/auth_spec.rb +112 -0
  102. data/spec/routes/oauth_spec.rb +134 -0
  103. data/spec/spec_helper.rb +68 -0
  104. data/spec/support/spec_app.rb +9 -0
  105. data/spec/support/views/admin.erb +5 -0
  106. metadata +348 -0
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Types
5
+ end
6
+ end
7
+
8
+ require_relative 'types/base_object'
9
+ require_relative 'types/base_enum'
10
+ require_relative 'types/base_input_object'
11
+ require_relative 'types/identity_provider_service'
12
+ require_relative 'types/identity_provider'
13
+ require_relative 'types/enterprise_account'
14
+ require_relative 'types/oauth_client'
15
+ require_relative 'types/user'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class BaseEnum < ::GraphQL::Schema::Enum
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class BaseInputObject < ::GraphQL::Schema::InputObject
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class BaseObject < ::GraphQL::Schema::Object
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class EnterpriseAccount < Types::BaseObject
9
+ description 'An Account for a company that wishes to use SAML via Osso'
10
+ implements ::GraphQL::Types::Relay::Node
11
+
12
+ global_id_field :gid
13
+ field :id, ID, null: false
14
+ field :name, String, null: false
15
+ field :domain, String, null: false
16
+ field :identity_providers, [Types::IdentityProvider], null: true
17
+ field :status, String, null: false
18
+
19
+ def status
20
+ 'active'
21
+ end
22
+
23
+ def identity_providers
24
+ object.identity_providers
25
+ end
26
+
27
+ def self.authorized?(object, context)
28
+ super && (context[:scope] == :admin || object.domain == context[:scope])
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class IdentityProvider < Types::BaseObject
9
+ description 'Represents a SAML based IDP instance for an EnterpriseAccount'
10
+ implements ::GraphQL::Types::Relay::Node
11
+
12
+ global_id_field :gid
13
+ field :id, ID, null: false
14
+ field :enterprise_account_id, ID, null: false
15
+ field :service, Types::IdentityProviderService, null: true
16
+ field :domain, String, null: false
17
+ field :acs_url, String, null: false
18
+ field :sso_url, String, null: true
19
+ field :sso_cert, String, null: true
20
+ field :configured, Boolean, null: false
21
+ field :documentation_pdf_url, String, null: true
22
+
23
+ def configured
24
+ !!(@object.sso_url && @object.sso_cert)
25
+ end
26
+
27
+ def documentation_pdf_url
28
+ ENV['BASE_URL'] + '/identity_provider/documentation/' + @object.id
29
+ end
30
+
31
+ def self.authorized?(object, context)
32
+ super && (context[:scope] == :admin || object.domain == context[:scope])
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Types
6
+ class IdentityProviderService < BaseEnum
7
+ value('AZURE', 'Microsoft Azure Identity Provider', value: 'Osso::Models::AzureSamlProvider')
8
+ value('OKTA', 'Okta Identity Provider', value: 'Osso::Models::OktaSamlProvider')
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class OAuthClient < Types::BaseObject
9
+ description 'An OAuth client used to consume Osso SAML users'
10
+ implements ::GraphQL::Types::Relay::Node
11
+
12
+ global_id_field :gid
13
+ field :id, ID, null: false
14
+ field :name, String, null: false
15
+ field :client_id, String, null: false
16
+ field :client_secret, String, null: false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
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
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Helpers
5
+ module Auth
6
+ attr_accessor :current_scope
7
+
8
+ def enterprise_protected!(domain = nil)
9
+ return if admin_authorized?
10
+ return if enterprise_authorized?(domain)
11
+
12
+ halt 401 if request.post?
13
+
14
+ redirect ENV['JWT_URL']
15
+ end
16
+
17
+ def enterprise_authorized?(_domain)
18
+ payload, _args = JWT.decode(
19
+ token,
20
+ ENV['JWT_HMAC_SECRET'],
21
+ true,
22
+ { algorithm: 'HS256' },
23
+ )
24
+
25
+ @current_scope = payload['scope']
26
+
27
+ true
28
+ rescue JWT::DecodeError
29
+ false
30
+ end
31
+
32
+ def admin_protected!
33
+ return if admin_authorized?
34
+
35
+ redirect ENV['JWT_URL']
36
+ end
37
+
38
+ def admin_authorized?
39
+ payload, _args = JWT.decode(
40
+ token,
41
+ ENV['JWT_HMAC_SECRET'],
42
+ true,
43
+ { algorithm: 'HS256' },
44
+ )
45
+
46
+ if payload['scope'] == 'admin'
47
+ @current_scope = :admin
48
+ return true
49
+ end
50
+
51
+ false
52
+ rescue JWT::DecodeError
53
+ false
54
+ end
55
+
56
+ def token
57
+ request.env['admin_token'] || session['admin_token'] || request['admin_token']
58
+ end
59
+
60
+ def chomp_token
61
+ return unless request['admin_token'].present?
62
+
63
+ session['admin_token'] = request['admin_token']
64
+
65
+ return if request.post?
66
+
67
+ redirect request.path
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Helpers
5
+ end
6
+ end
7
+
8
+ require_relative 'auth'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/contrib'
4
+
5
+ module Osso
6
+ module AppConfig
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ use Rack::JSONBodyParser
10
+ use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
11
+
12
+ error ActiveRecord::RecordNotFound do
13
+ status 404
14
+ end
15
+
16
+ set :root, Dir.pwd
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Osso
6
+ module OAuth2Token
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ cattr_accessor :default_lifetime
10
+ self.default_lifetime = 1.minute
11
+ belongs_to :user
12
+ belongs_to :oauth_client
13
+
14
+ before_validation :setup, on: :create
15
+ validates :oauth_client, :expires_at, presence: true
16
+ validates :token, presence: true, uniqueness: true
17
+
18
+ scope :valid, -> { where('expires_at > ?', Time.now.utc) }
19
+ end
20
+ end
21
+
22
+ def expires_in
23
+ (expires_at - Time.now.utc).to_i
24
+ end
25
+
26
+ def expired!
27
+ self.expires_at = Time.now.utc
28
+ save!
29
+ end
30
+
31
+ private
32
+
33
+ def setup
34
+ self.token = SecureRandom.hex(32)
35
+ self.expires_at ||= default_lifetime.from_now
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/MethodLength
4
+
5
+ module Osso
6
+ module RouteMap
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ use Osso::Admin
10
+ use Osso::Auth
11
+ use Osso::Oauth
12
+
13
+ post '/graphql' do
14
+ enterprise_protected!
15
+
16
+ result = Osso::GraphQL::Schema.execute(
17
+ params[:query],
18
+ variables: params[:variables],
19
+ context: { scope: current_scope },
20
+ )
21
+
22
+ json result
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ # rubocop:enable Metrics/MethodLength
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ class AccessToken < ::ActiveRecord::Base
6
+ include OAuth2Token
7
+ self.default_lifetime = 10.minutes
8
+ belongs_to :refresh_token
9
+
10
+ def to_bearer_token
11
+ Rack::OAuth2::AccessToken::Bearer.new(
12
+ access_token: token,
13
+ expires_in: expires_in,
14
+ )
15
+ end
16
+
17
+ private
18
+
19
+ def setup
20
+ super
21
+ return unless refresh_token
22
+
23
+ self.user = refresh_token.user
24
+ self.client = refresh_token.client
25
+ self.expires_at = [expires_at, refresh_token.expires_at].min
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ class AuthorizationCode < ActiveRecord::Base
6
+ include OAuth2Token
7
+
8
+ def access_token
9
+ @access_token ||= expired! &&
10
+ user.access_tokens.create(oauth_client: oauth_client)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ # Base class for Enterprises. This should map one-to-one with
6
+ # your own Account model. Persisting the EnterpriseAccount id
7
+ # in your application's database is recommended. The table also
8
+ # includes fields for external IDs such that you can persist
9
+ # your ID for an account in your Osso instance.
10
+ class EnterpriseAccount < ActiveRecord::Base
11
+ belongs_to :oauth_client
12
+ has_many :users
13
+ has_many :identity_providers
14
+
15
+ def single_provider?
16
+ identity_providers.one?
17
+ end
18
+
19
+ def provider
20
+ return nil unless single_provider?
21
+
22
+ identity_providers.first
23
+ end
24
+
25
+ alias identity_provider provider
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ # Base class for SAML Providers
6
+ class IdentityProvider < ActiveRecord::Base
7
+ NAME_FORMAT = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
8
+ belongs_to :enterprise_account
9
+ belongs_to :oauth_client
10
+ has_many :users
11
+
12
+ def name
13
+ service.titlecase
14
+ # raise(
15
+ # NoMethodError,
16
+ # '#name must be defined on each provider specific subclass',
17
+ # )
18
+ end
19
+
20
+ def saml_options
21
+ attributes.slice(
22
+ 'domain',
23
+ 'idp_cert',
24
+ 'idp_sso_target_url',
25
+ ).symbolize_keys
26
+ end
27
+
28
+ # def saml_options
29
+ # raise(
30
+ # NoMethodError,
31
+ # '#saml_options must be defined on each provider specific subclass',
32
+ # )
33
+ # end
34
+
35
+ def assertion_consumer_service_url
36
+ [
37
+ ENV.fetch('BASE_URL'),
38
+ 'auth',
39
+ 'saml',
40
+ id,
41
+ 'callback',
42
+ ].join('/')
43
+ end
44
+
45
+ alias acs_url assertion_consumer_service_url
46
+ end
47
+ end
48
+ end