osso 0.0.3.7

Sign up to get free protection for your applications and to get access to all the features.
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