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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/activerecord'
4
+
5
+ module Osso
6
+ module Models
7
+ end
8
+ end
9
+
10
+ require_relative 'access_token'
11
+ require_relative 'authorization_code'
12
+ require_relative 'enterprise_account'
13
+ require_relative 'oauth_client'
14
+ require_relative 'redirect_uri'
15
+ require_relative 'identity_provider'
16
+ require_relative 'user'
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ module Osso
5
+ module Models
6
+ class OauthClient < ActiveRecord::Base
7
+ has_many :access_tokens
8
+ has_many :refresh_tokens
9
+ has_many :identity_providers
10
+ has_many :redirect_uris
11
+
12
+ before_validation :setup, on: :create
13
+ validates :name, :secret, presence: true
14
+ validates :identifier, presence: true, uniqueness: true
15
+
16
+ def default_redirect_uri
17
+ redirect_uris.find(&:primary)
18
+ end
19
+
20
+ def redirect_uri_values
21
+ redirect_uris.map(&:uri)
22
+ end
23
+
24
+ private
25
+
26
+ def setup
27
+ self.identifier = SecureRandom.hex(16)
28
+ self.secret = SecureRandom.hex(64)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ class RedirectUri < ActiveRecord::Base
6
+ belongs_to :oauth_client
7
+
8
+ # TODO
9
+ # before_validation :set_primary, on: :creaet, :update
10
+
11
+ private
12
+
13
+ def set_primary
14
+ if primary_was.true? && primary.false?
15
+
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
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
+ before_create :create_enterprise_account
13
+
14
+ # def name
15
+ # raise(
16
+ # NoMethodError,
17
+ # '#name must be defined on each provider specific subclass',
18
+ # )
19
+ # end
20
+
21
+ # def saml_options
22
+ # raise(
23
+ # NoMethodError,
24
+ # '#saml_options must be defined on each provider specific subclass',
25
+ # )
26
+ # end
27
+
28
+ def assertion_consumer_service_url
29
+ [
30
+ ENV.fetch('BASE_URL'),
31
+ 'auth',
32
+ 'saml',
33
+ id,
34
+ 'callback',
35
+ ].join('/')
36
+ end
37
+
38
+ alias acs_url assertion_consumer_service_url
39
+
40
+ def create_enterprise_account
41
+ return if enterprise_account_id
42
+
43
+ self.enterprise_account = Models::EnterpriseAccount.create(
44
+ domain: domain,
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ # Subclass for Azure / ADFS IDP instances
6
+ class AzureSamlProvider < Models::IdentityProvider
7
+ def name
8
+ 'Azure'
9
+ end
10
+
11
+ def saml_options
12
+ attributes.slice(
13
+ 'domain',
14
+ 'idp_cert',
15
+ 'idp_sso_target_url',
16
+ ).merge(
17
+ issuer: "id:#{id}",
18
+ ).symbolize_keys
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ # Subclass for Okta IDP instances
6
+ class OktaSamlProvider < Models::IdentityProvider
7
+ def name
8
+ 'Okta'
9
+ end
10
+
11
+ def saml_options
12
+ attributes.slice(
13
+ 'domain',
14
+ 'idp_cert',
15
+ 'idp_sso_target_url',
16
+ ).merge(
17
+ issuer: id,
18
+ name_identifier_format: NAME_FORMAT,
19
+ ).symbolize_keys
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ class User < ActiveRecord::Base
6
+ belongs_to :enterprise_account
7
+ belongs_to :identity_provider
8
+ has_many :authorization_codes, dependent: :delete_all
9
+ has_many :access_tokens, dependent: :delete_all
10
+
11
+ def oauth_client
12
+ identity_provider.oauth_client
13
+ end
14
+
15
+ def as_json(*)
16
+ {
17
+ email: email,
18
+ id: id,
19
+ idp: identity_provider.name,
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ load 'active_record/railties/databases.rake'
4
+ require "sinatra/activerecord/rake/activerecord_#{ActiveRecord::VERSION::MAJOR}"
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+
5
+ module Osso
6
+ class Admin < Sinatra::Base
7
+ include AppConfig
8
+ helpers Helpers::Auth
9
+ register Sinatra::Namespace
10
+
11
+ before do
12
+ chomp_token
13
+ end
14
+
15
+ namespace '/admin' do
16
+ get '' do
17
+ admin_protected!
18
+
19
+ erb :admin
20
+ end
21
+
22
+ get '/enterprise' do
23
+ admin_protected!
24
+
25
+ erb :admin
26
+ end
27
+
28
+ get '/enterprise/:domain' do
29
+ enterprise_protected!(params[:domain])
30
+
31
+ erb :admin
32
+ end
33
+
34
+ get '/config' do
35
+ admin_protected!
36
+
37
+ erb :admin
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'omniauth'
5
+ require 'omniauth-multi-provider'
6
+ require 'omniauth-saml'
7
+
8
+ module Osso
9
+ class Auth < Sinatra::Base
10
+ include AppConfig
11
+ register Sinatra::Namespace
12
+
13
+ UUID_REGEXP =
14
+ /[0-9a-f]{8}-[0-9a-f]{3,4}-[0-9a-f]{4}-[0-9a-f]{3,4}-[0-9a-f]{12}/.
15
+ freeze
16
+
17
+ def self.internal_redirect?(env)
18
+ env['HTTP_REFERER']&.match(env['SERVER_NAME'])
19
+ end
20
+
21
+ use OmniAuth::Builder do
22
+ OmniAuth::MultiProvider.register(
23
+ self,
24
+ provider_name: 'saml',
25
+ identity_provider_id_regex: UUID_REGEXP,
26
+ path_prefix: '/saml',
27
+ callback_suffix: 'callback',
28
+ ) do |identity_provider_id, _env|
29
+ provider = Models::IdentityProvider.find(identity_provider_id)
30
+ provider.saml_options
31
+ end
32
+ end
33
+
34
+ namespace '/auth' do
35
+ # Enterprise users are sent here after authenticating against
36
+ # their Identity Provider. We find or create a user record,
37
+ # and then create an authorization code for that user. The user
38
+ # is redirected back to your application with this code
39
+ # as a URL query param, which you then exhange for an access token
40
+ post '/saml/:id/callback' do
41
+ provider = Models::IdentityProvider.find(params[:id])
42
+ oauth_client = provider.oauth_client
43
+ redirect_uri = env['redirect_uri'] || oauth_client.default_redirect_uri.uri
44
+
45
+ attributes = env['omniauth.auth']&.
46
+ extra&.
47
+ response_object&.
48
+ attributes
49
+
50
+ user = Models::User.where(
51
+ email: attributes[:email],
52
+ idp_id: attributes[:id],
53
+ ).first_or_create! do |new_user|
54
+ new_user.enterprise_account_id = provider.enterprise_account_id
55
+ new_user.identity_provider_id = provider.id
56
+ end
57
+
58
+ authorization_code = user.authorization_codes.create!(
59
+ oauth_client: oauth_client,
60
+ redirect_uri: redirect_uri,
61
+ )
62
+
63
+ redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{session[:oauth_state]}")
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/oauth2'
4
+
5
+ module Osso
6
+ class Oauth < Sinatra::Base
7
+ include AppConfig
8
+ register Sinatra::Namespace
9
+ # rubocop:disable Metrics/BlockLength
10
+ namespace '/oauth' do
11
+ # Send your users here in order to being an authentication
12
+ # flow. This flow follows the authorization grant oauth
13
+ # spec with one exception - you must also pass the domain
14
+ # of the user who wants to sign in.
15
+ get '/authorize' do
16
+ @enterprise = Models::EnterpriseAccount.
17
+ includes(:identity_providers).
18
+ find_by!(domain: params[:domain])
19
+
20
+ Rack::OAuth2::Server::Authorize.new do |req, _res|
21
+ client = Models::OauthClient.find_by!(identifier: req.client_id)
22
+ req.verify_redirect_uri!(client.redirect_uri_values)
23
+ end.call(env)
24
+
25
+ if @enterprise.single_provider?
26
+ session[:oauth_state] = params[:state]
27
+ redirect "/auth/saml/#{@enterprise.provider.id}"
28
+ end
29
+
30
+ # TODO: multiple provider support
31
+ # erb :multiple_providers
32
+
33
+ rescue Rack::OAuth2::Server::Authorize::BadRequest => e
34
+ @error = e
35
+ return erb :error
36
+ end
37
+
38
+ # Exchange an authorization code token for an access token.
39
+ # In addition to the token, you must include all paramaters
40
+ # required by Oauth spec: redirect_uri, client ID, and client secret
41
+ post '/token' do
42
+ Rack::OAuth2::Server::Token.new do |req, res|
43
+ code = Models::AuthorizationCode.
44
+ find_by_token!(params[:code])
45
+ client = Models::OauthClient.find_by!(identifier: req.client_id)
46
+ req.invalid_client! if client.secret != req.client_secret
47
+ req.invalid_grant! if code.redirect_uri != req.redirect_uri
48
+ res.access_token = code.access_token.to_bearer_token
49
+ end.call(env)
50
+ end
51
+
52
+ # Use the access token to request a user profile
53
+ get '/me' do
54
+ json Models::AccessToken.
55
+ includes(:user).
56
+ valid.
57
+ find_by_token!(params[:access_token]).
58
+ user
59
+ end
60
+ end
61
+ end
62
+ end
63
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/contrib'
4
+ require 'sinatra/base'
5
+ require 'sinatra/contrib'
6
+ require 'sinatra/json'
7
+
8
+ require_relative 'admin'
9
+ require_relative 'auth'
10
+ require_relative 'oauth'
@@ -0,0 +1 @@
1
+ <%= @error %>
@@ -0,0 +1 @@
1
+ multiple providers
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ VERSION = '0.0.3.7'
5
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/activerecord'
4
+ require 'sinatra/activerecord/rake'
5
+ require 'osso'
6
+
7
+ namespace :osso do
8
+ desc 'Bootstrap Osso data for a deployment'
9
+ task :bootstrap do
10
+ %w[Production Staging Development].each do |environement|
11
+ Osso::Models::OauthClient.create!(
12
+ name: environement,
13
+ )
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/osso/version'
4
+
5
+ # rubocop:disable Metrics/BlockLength
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'osso'
8
+ spec.version = Osso::VERSION
9
+ spec.authors = ['Sam Bauch']
10
+ spec.email = ['sbauch@gmail.com']
11
+
12
+ spec.summary = 'Main functionality for Osso'
13
+ spec.description = 'This gem includes the main functionality for Osso apps,'
14
+ spec.homepage = 'https://github.com/enterprise-oss/osso-rb'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
16
+ spec.license = 'MIT'
17
+
18
+ spec.add_runtime_dependency 'activesupport', '>= 6.0.3.2'
19
+ spec.add_runtime_dependency 'graphql'
20
+ spec.add_runtime_dependency 'jwt'
21
+ spec.add_runtime_dependency 'omniauth-multi-provider'
22
+ spec.add_runtime_dependency 'omniauth-saml'
23
+ spec.add_runtime_dependency 'rack', '>= 2.1.4'
24
+ spec.add_runtime_dependency 'rack-contrib'
25
+ spec.add_runtime_dependency 'rack-oauth2'
26
+ spec.add_runtime_dependency 'rake'
27
+ spec.add_runtime_dependency 'sinatra'
28
+ spec.add_runtime_dependency 'sinatra-activerecord'
29
+ spec.add_runtime_dependency 'sinatra-contrib'
30
+
31
+ spec.add_development_dependency 'bundler', '~> 2.1'
32
+ spec.add_development_dependency 'pry'
33
+
34
+ spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
35
+ spec.files = `git ls-files`.split("\n")
36
+ spec.test_files = `git ls-files -- {spec}/*`.split("\n")
37
+ spec.bindir = 'bin'
38
+ spec.require_paths = ['lib']
39
+ end
40
+ # rubocop:enable Metrics/BlockLength