osso 0.0.2.8
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.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.buildkite/hooks/environment +9 -0
- data/.buildkite/hooks/pre-command +7 -0
- data/.buildkite/pipeline.yml +3 -0
- data/.buildkite/template.yml +5 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.rubocop.yml +82 -0
- data/CODE_OF_CONDUCT.md +130 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +174 -0
- data/LICENSE +111 -0
- data/README.md +2 -0
- data/Rakefile +14 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/config/database.yml +14 -0
- data/lib/.DS_Store +0 -0
- data/lib/osso/Rakefile +13 -0
- data/lib/osso/db/migrate/20190909230109_enable_uuid.rb +7 -0
- data/lib/osso/db/migrate/20200328135750_create_users.rb +12 -0
- data/lib/osso/db/migrate/20200328143303_create_oauth_tables.rb +57 -0
- data/lib/osso/db/migrate/20200411144528_create_saml_providers.rb +13 -0
- data/lib/osso/db/migrate/20200411184535_add_provider_id_to_users.rb +7 -0
- data/lib/osso/db/migrate/20200411192645_create_enterprise_accounts.rb +15 -0
- data/lib/osso/db/migrate/20200413132407_add_oauth_clients.rb +13 -0
- data/lib/osso/db/migrate/20200413142511_create_authorization_codes.rb +15 -0
- data/lib/osso/db/migrate/20200413153029_add_oauth_client_reference_to_saml_providers.rb +5 -0
- data/lib/osso/db/migrate/20200413163451_create_access_tokens.rb +13 -0
- data/lib/osso/db/migrate/20200501203026_drop_null_constraints_from_saml_provider.rb +7 -0
- data/lib/osso/db/migrate/20200501204047_drop_acs_url.rb +5 -0
- data/lib/osso/db/migrate/20200502120616_create_redirect_uris_and_drop_from_oauth_clients.rb +13 -0
- data/lib/osso/db/migrate/20200502135008_add_oauth_client_id_to_enterprise_account.rb +5 -0
- data/lib/osso/db/migrate/20200601131227_drop_null_constraint_from_saml_providers_provider.rb +7 -0
- data/lib/osso/db/schema.rb +132 -0
- data/lib/osso/helpers/auth.rb +67 -0
- data/lib/osso/helpers/helpers.rb +6 -0
- data/lib/osso/lib/app_config.rb +20 -0
- data/lib/osso/lib/oauth2_token.rb +38 -0
- data/lib/osso/models/access_token.rb +29 -0
- data/lib/osso/models/authorization_code.rb +14 -0
- data/lib/osso/models/enterprise_account.rb +28 -0
- data/lib/osso/models/models.rb +16 -0
- data/lib/osso/models/oauth_client.rb +32 -0
- data/lib/osso/models/redirect_uri.rb +20 -0
- data/lib/osso/models/saml_provider.rb +52 -0
- data/lib/osso/models/saml_providers/azure_saml_provider.rb +22 -0
- data/lib/osso/models/saml_providers/okta_saml_provider.rb +23 -0
- data/lib/osso/models/user.rb +24 -0
- data/lib/osso/rake.rb +4 -0
- data/lib/osso/routes/admin.rb +42 -0
- data/lib/osso/routes/auth.rb +64 -0
- data/lib/osso/routes/oauth.rb +57 -0
- data/lib/osso/routes/routes.rb +10 -0
- data/lib/osso/routes/views/error.erb +1 -0
- data/lib/osso/routes/views/multiple_providers.erb +1 -0
- data/lib/osso/version.rb +5 -0
- data/lib/osso.rb +9 -0
- data/lib/tasks/bootstrap.rake +17 -0
- data/osso-rb.gemspec +40 -0
- data/spec/factories/authorization_code.rb +10 -0
- data/spec/factories/enterprise_account.rb +45 -0
- data/spec/factories/oauth_client.rb +12 -0
- data/spec/factories/redirect_uri.rb +14 -0
- data/spec/factories/saml_providers.rb +46 -0
- data/spec/factories/user.rb +18 -0
- data/spec/models/azure_saml_provider_spec.rb +19 -0
- data/spec/models/okta_saml_provider_spec.rb +20 -0
- data/spec/models/saml_provider_spec.rb +31 -0
- data/spec/routes/admin_spec.rb +57 -0
- data/spec/routes/app_spec.rb +6 -0
- data/spec/routes/auth_spec.rb +112 -0
- data/spec/routes/oauth_spec.rb +134 -0
- data/spec/spec_helper.rb +65 -0
- data/spec/support/vcr_cassettes/okta_saml_callback.yml +59 -0
- metadata +302 -0
@@ -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::SamlProvider
|
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::SamlProvider
|
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 :saml_provider
|
8
|
+
has_many :authorization_codes, dependent: :delete_all
|
9
|
+
has_many :access_tokens, dependent: :delete_all
|
10
|
+
|
11
|
+
def oauth_client
|
12
|
+
saml_provider.oauth_client
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json(*)
|
16
|
+
{
|
17
|
+
email: email,
|
18
|
+
id: id,
|
19
|
+
idp: saml_provider.name,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/osso/rake.rb
ADDED
@@ -0,0 +1,42 @@
|
|
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
|
+
|
10
|
+
before do
|
11
|
+
chomp_token
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/' do
|
15
|
+
admin_protected!
|
16
|
+
|
17
|
+
erb :'public/index'
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/enterprise' do
|
21
|
+
admin_protected!
|
22
|
+
|
23
|
+
erb :admin
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/enterprise/:domain' do
|
27
|
+
enterprise_protected!(params[:domain])
|
28
|
+
|
29
|
+
@enterprise = Models::EnterpriseAccount.where(
|
30
|
+
domain: params[:domain],
|
31
|
+
).first_or_create
|
32
|
+
|
33
|
+
erb :admin
|
34
|
+
end
|
35
|
+
|
36
|
+
get '/config' do
|
37
|
+
admin_protected!
|
38
|
+
|
39
|
+
erb :admin
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,64 @@
|
|
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
|
+
|
12
|
+
UUID_REGEXP =
|
13
|
+
/[0-9a-f]{8}-[0-9a-f]{3,4}-[0-9a-f]{4}-[0-9a-f]{3,4}-[0-9a-f]{12}/.
|
14
|
+
freeze
|
15
|
+
|
16
|
+
def self.internal_redirect?(env)
|
17
|
+
env['HTTP_REFERER']&.match(env['SERVER_NAME'])
|
18
|
+
end
|
19
|
+
|
20
|
+
use OmniAuth::Builder do
|
21
|
+
OmniAuth::MultiProvider.register(
|
22
|
+
self,
|
23
|
+
provider_name: 'saml',
|
24
|
+
identity_provider_id_regex: UUID_REGEXP,
|
25
|
+
path_prefix: '/saml',
|
26
|
+
callback_suffix: 'callback',
|
27
|
+
) do |saml_provider_id, _env|
|
28
|
+
provider = Models::SamlProvider.find(saml_provider_id)
|
29
|
+
provider.saml_options
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Enterprise users are sent here after authenticating against
|
34
|
+
# their Identity Provider. We find or create a user record,
|
35
|
+
# and then create an authorization code for that user. The user
|
36
|
+
# is redirected back to your application with this code
|
37
|
+
# as a URL query param, which you then exhange for an access token
|
38
|
+
post '/saml/:id/callback' do
|
39
|
+
provider = Models::SamlProvider.find(params[:id])
|
40
|
+
oauth_client = provider.oauth_client
|
41
|
+
redirect_uri = env['redirect_uri'] || oauth_client.default_redirect_uri.uri
|
42
|
+
|
43
|
+
attributes = env['omniauth.auth']&.
|
44
|
+
extra&.
|
45
|
+
response_object&.
|
46
|
+
attributes
|
47
|
+
|
48
|
+
user = Models::User.where(
|
49
|
+
email: attributes[:email],
|
50
|
+
idp_id: attributes[:id],
|
51
|
+
).first_or_create! do |new_user|
|
52
|
+
new_user.enterprise_account_id = provider.enterprise_account_id
|
53
|
+
new_user.saml_provider_id = provider.id
|
54
|
+
end
|
55
|
+
|
56
|
+
authorization_code = user.authorization_codes.create!(
|
57
|
+
oauth_client: oauth_client,
|
58
|
+
redirect_uri: redirect_uri,
|
59
|
+
)
|
60
|
+
|
61
|
+
redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{session[:oauth_state]}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack/oauth2'
|
4
|
+
|
5
|
+
module Osso
|
6
|
+
class Oauth < Sinatra::Base
|
7
|
+
include AppConfig
|
8
|
+
# Send your users here in order to being an authentication
|
9
|
+
# flow. This flow follows the authorization grant oauth
|
10
|
+
# spec with one exception - you must also pass the domain
|
11
|
+
# of the user who wants to sign in.
|
12
|
+
get '/authorize' do
|
13
|
+
@enterprise = Models::EnterpriseAccount.
|
14
|
+
includes(:saml_providers).
|
15
|
+
find_by!(domain: params[:domain])
|
16
|
+
|
17
|
+
Rack::OAuth2::Server::Authorize.new do |req, _res|
|
18
|
+
client = Models::OauthClient.find_by!(identifier: req.client_id)
|
19
|
+
req.verify_redirect_uri!(client.redirect_uri_values)
|
20
|
+
end.call(env)
|
21
|
+
|
22
|
+
if @enterprise.single_provider?
|
23
|
+
session[:oauth_state] = params[:state]
|
24
|
+
redirect "/auth/saml/#{@enterprise.provider.id}"
|
25
|
+
end
|
26
|
+
|
27
|
+
erb :multiple_providers
|
28
|
+
|
29
|
+
rescue Rack::OAuth2::Server::Authorize::BadRequest => e
|
30
|
+
@error = e
|
31
|
+
return erb :error
|
32
|
+
end
|
33
|
+
|
34
|
+
# Exchange an authorization code token for an access token.
|
35
|
+
# In addition to the token, you must include all paramaters
|
36
|
+
# required by Oauth spec: redirect_uri, client ID, and client secret
|
37
|
+
post '/token' do
|
38
|
+
Rack::OAuth2::Server::Token.new do |req, res|
|
39
|
+
code = Models::AuthorizationCode.
|
40
|
+
find_by_token!(params[:code])
|
41
|
+
client = Models::OauthClient.find_by!(identifier: req.client_id)
|
42
|
+
req.invalid_client! if client.secret != req.client_secret
|
43
|
+
req.invalid_grant! if code.redirect_uri != req.redirect_uri
|
44
|
+
res.access_token = code.access_token.to_bearer_token
|
45
|
+
end.call(env)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Use the access token to request a user profile
|
49
|
+
get '/me' do
|
50
|
+
json Models::AccessToken.
|
51
|
+
includes(:user).
|
52
|
+
valid.
|
53
|
+
find_by_token!(params[:access_token]).
|
54
|
+
user
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= @error %>
|
@@ -0,0 +1 @@
|
|
1
|
+
multiple providers
|
data/lib/osso/version.rb
ADDED
data/lib/osso.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sinatra/activerecord'
|
4
|
+
require 'sinatra/activerecord/rake'
|
5
|
+
require 'osso'
|
6
|
+
require 'dotenv/tasks'
|
7
|
+
|
8
|
+
namespace :osso do
|
9
|
+
desc 'Bootstrap Osso data for a deployment'
|
10
|
+
task bootstrap: :dotenv do
|
11
|
+
%w[Production Staging Development].each do |environement|
|
12
|
+
Osso::Models::OauthClient.create!(
|
13
|
+
name: environement,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/osso-rb.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/osso/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'osso'
|
7
|
+
spec.version = Osso::VERSION
|
8
|
+
spec.authors = ['Sam Bauch']
|
9
|
+
spec.email = ['sbauch@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Main functionality for Osso'
|
12
|
+
spec.description = 'This gem includes the main functionality for Osso apps,'
|
13
|
+
spec.homepage = 'https://github.com/enterprise-oss/osso-rb'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.add_runtime_dependency 'activesupport', '>= 6.0.3.2'
|
18
|
+
spec.add_runtime_dependency 'jwt'
|
19
|
+
spec.add_runtime_dependency 'omniauth-multi-provider'
|
20
|
+
spec.add_runtime_dependency 'omniauth-saml'
|
21
|
+
spec.add_runtime_dependency 'rack', '>= 2.1.4'
|
22
|
+
spec.add_runtime_dependency 'rack-contrib'
|
23
|
+
spec.add_runtime_dependency 'rack-oauth2'
|
24
|
+
spec.add_runtime_dependency 'rake'
|
25
|
+
spec.add_runtime_dependency 'sinatra'
|
26
|
+
spec.add_runtime_dependency 'sinatra-activerecord'
|
27
|
+
spec.add_runtime_dependency 'sinatra-contrib'
|
28
|
+
|
29
|
+
spec.add_development_dependency 'bundler', '~> 2.1'
|
30
|
+
spec.add_development_dependency 'pry'
|
31
|
+
|
32
|
+
# Specify which files should be added to the gem when it is released.
|
33
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
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.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
39
|
+
spec.require_paths = ['lib']
|
40
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
factory :enterprise_account, class: Osso::Models::EnterpriseAccount do
|
5
|
+
id { SecureRandom.uuid }
|
6
|
+
domain { Faker::Internet.domain_name }
|
7
|
+
oauth_client
|
8
|
+
end
|
9
|
+
|
10
|
+
factory :enterprise_with_okta, parent: :enterprise_account do
|
11
|
+
after :create do |enterprise|
|
12
|
+
create(
|
13
|
+
:okta_saml_provider,
|
14
|
+
domain: enterprise.domain,
|
15
|
+
enterprise_account_id: enterprise.id,
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
factory :enterprise_with_azure, parent: :enterprise_account do
|
21
|
+
after :create do |enterprise|
|
22
|
+
create(
|
23
|
+
:azure_saml_provider,
|
24
|
+
domain: enterprise.domain,
|
25
|
+
enterprise_account_id: enterprise.id,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
factory :enterprise_with_multiple_providers, parent: :enterprise_account do
|
31
|
+
after :create do |enterprise|
|
32
|
+
create(
|
33
|
+
:okta_saml_provider,
|
34
|
+
domain: enterprise.domain,
|
35
|
+
enterprise_account_id: enterprise.id,
|
36
|
+
)
|
37
|
+
|
38
|
+
create(
|
39
|
+
:azure_saml_provider,
|
40
|
+
domain: enterprise.domain,
|
41
|
+
enterprise_account_id: enterprise.id,
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
factory :oauth_client, class: Osso::Models::OauthClient do
|
5
|
+
id { SecureRandom.uuid }
|
6
|
+
name { Faker::Internet.domain_name }
|
7
|
+
after(:create) do |client|
|
8
|
+
create(:primary_redirect_uri, oauth_client: client)
|
9
|
+
create(:redirect_uri, oauth_client: client)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
factory :redirect_uri, class: Osso::Models::RedirectUri do
|
5
|
+
id { SecureRandom.uuid }
|
6
|
+
uri { Faker::Internet.url }
|
7
|
+
primary { false }
|
8
|
+
oauth_client
|
9
|
+
end
|
10
|
+
|
11
|
+
factory :primary_redirect_uri, parent: :redirect_uri do
|
12
|
+
primary { true }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
factory :saml_provider, class: Osso::Models::SamlProvider do
|
5
|
+
id { SecureRandom.uuid }
|
6
|
+
domain { Faker::Internet.domain_name }
|
7
|
+
oauth_client
|
8
|
+
idp_cert do
|
9
|
+
<<~CERT
|
10
|
+
-----BEGIN CERTIFICATE-----
|
11
|
+
MIIDpDCCAoygAwIBAgIGAXEiD4LlMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
|
12
|
+
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
|
13
|
+
MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xNjIwMjQxHDAaBgkqhkiG9w0BCQEW
|
14
|
+
DWluZm9Ab2t0YS5jb20wHhcNMjAwMzI4MTY1MTU0WhcNMzAwMzI4MTY1MjU0WjCBkjELMAkGA1UE
|
15
|
+
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
|
16
|
+
BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTYyMDI0MRwwGgYJ
|
17
|
+
KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
18
|
+
wsnP4UTfv3bxR5Jh0at51Dqjj+fKxFznzFW3XA5NbF2SlRLjeYcvj3+47TC0eP6xOsLWfnvdnx4v
|
19
|
+
dd9Ufn7jDCo5pL3JykMVEh2I0szF3RLC+a532ArcwgU9Px48+rWVwPkASS7l4NHAM4+gOBHJMQt2
|
20
|
+
AMohPT0kU41P8BEPzfwhNyiEXR66JNZIJUE8fM3Vpgnxm/VSwYzJf0NfOyfxv8JczF0zkDbpE7Tk
|
21
|
+
3Ww/PFFLoMxWzanWGJQ+blnhv6UV6H4fcfAbcwAplOdIVHjS2ghYBvYNGahuFxjia0+6csyZGrt8
|
22
|
+
H4XmR5Dr+jXY5K1b1VOA0k19/FCnHHN/smn25wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBgD9NE
|
23
|
+
4OCuR1+vucV8S1T6XXIL2hB7bXBAZEVHZ1aErRzktgXAMgVwG267vIkD5VOXBiTy9yNU5LK6G3k2
|
24
|
+
zewU190sL1dMfyPnoVZyn94nvwe9A+on0tmZdmk00xirKk3FJdacnZNE9Dl/afIrcNf6xAm0WsU9
|
25
|
+
kbMiRwwvjO4TAiygDQzbrRC8ZfmT3hpBa3aTUzAccrvEQcgarLk4r7UjXP7a2mCN3UIIh+snN2Ms
|
26
|
+
vXHL0r6fM3xbniz+5lleWtPFw73yySBc8znkWZ4Tn8Lh0r6o5nCRYbr2REUB7ZIfiIyBbZxIp4kv
|
27
|
+
a+habbnQDFiNVzEd8OPXHh4EqLxOPDRW
|
28
|
+
-----END CERTIFICATE-----
|
29
|
+
CERT
|
30
|
+
end
|
31
|
+
|
32
|
+
factory :okta_saml_provider, parent: :saml_provider, class: Osso::Models::OktaSamlProvider do
|
33
|
+
provider { 'Osso::Models::OktaSamlProvider' }
|
34
|
+
idp_sso_target_url do
|
35
|
+
'https://dev-162024.okta.com/app/vcardmedev162024_rubydemo2_1/exk51326b3U1941Hf4x6/sso/saml'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
factory :azure_saml_provider, parent: :saml_provider, class: Osso::Models::AzureSamlProvider do
|
40
|
+
provider { 'Osso::Models::AzureSamlProvider' }
|
41
|
+
idp_sso_target_url do
|
42
|
+
'https://login.microsoftonline.com/0af6c610-c40c-4683-9ea4-f25e509b8172/saml2'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
factory :user, class: Osso::Models::User do
|
5
|
+
id { SecureRandom.uuid }
|
6
|
+
email { Faker::Internet.email }
|
7
|
+
idp_id { SecureRandom.hex(32) }
|
8
|
+
saml_provider { create(:okta_saml_provider) }
|
9
|
+
enterprise_account
|
10
|
+
after(:create) do |user|
|
11
|
+
create(
|
12
|
+
:authorization_code,
|
13
|
+
user: user,
|
14
|
+
redirect_uri: user.oauth_client.redirect_uri_values.sample,
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Osso::Models::AzureSamlProvider do
|
6
|
+
subject { create(:azure_saml_provider) }
|
7
|
+
|
8
|
+
describe '#saml_options' do
|
9
|
+
it 'returns the required args' do
|
10
|
+
expect(subject.saml_options).
|
11
|
+
to match(
|
12
|
+
domain: subject.domain,
|
13
|
+
idp_cert: subject.idp_cert,
|
14
|
+
idp_sso_target_url: subject.idp_sso_target_url,
|
15
|
+
issuer: "id:#{subject.id}",
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Osso::Models::OktaSamlProvider do
|
6
|
+
subject { create(:okta_saml_provider) }
|
7
|
+
|
8
|
+
describe '#saml_options' do
|
9
|
+
it 'returns the required args' do
|
10
|
+
expect(subject.saml_options).
|
11
|
+
to match(
|
12
|
+
domain: subject.domain,
|
13
|
+
idp_cert: subject.idp_cert,
|
14
|
+
idp_sso_target_url: subject.idp_sso_target_url,
|
15
|
+
issuer: subject.id,
|
16
|
+
name_identifier_format: described_class::NAME_FORMAT,
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Osso::Models::SamlProvider do
|
6
|
+
subject { create(:okta_saml_provider) }
|
7
|
+
|
8
|
+
describe '.create' do
|
9
|
+
it 'creates an enterprise account' do
|
10
|
+
domain = Faker::Internet.domain_name
|
11
|
+
|
12
|
+
provider = described_class.create(
|
13
|
+
domain: domain,
|
14
|
+
provider: 'Osso::Models::OktaSamlProvider',
|
15
|
+
)
|
16
|
+
|
17
|
+
expect(provider.enterprise_account).to be_a(Osso::Models::EnterpriseAccount)
|
18
|
+
expect(provider.enterprise_account.domain).to eq(domain)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#assertion_consumer_service_url' do
|
23
|
+
it 'returns the expected URI' do
|
24
|
+
ENV['BASE_URL'] = 'https://example.com'
|
25
|
+
|
26
|
+
expect(subject.assertion_consumer_service_url).to eq(
|
27
|
+
"https://example.com/auth/saml/#{subject.id}/callback",
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Osso::Admin do
|
6
|
+
let(:jwt_url) { 'https://foo.com/jwt' }
|
7
|
+
let(:jwt_hmac_secret) { SecureRandom.hex(32) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
ENV['JWT_URL'] = jwt_url
|
11
|
+
ENV['JWT_HMAC_SECRET'] = jwt_hmac_secret
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'get /admin' do
|
15
|
+
it 'redirects to JWT_URL without a session or token' do
|
16
|
+
get('/admin')
|
17
|
+
|
18
|
+
expect(last_response).to be_redirect
|
19
|
+
follow_redirect!
|
20
|
+
expect(last_request.url).to eq(jwt_url)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'redirects to JWT_URL with an invalid token' do
|
24
|
+
get('/admin', token: SecureRandom.hex(32))
|
25
|
+
|
26
|
+
expect(last_response).to be_redirect
|
27
|
+
follow_redirect!
|
28
|
+
expect(last_request.url).to eq(jwt_url)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'chomps the token and redirects to request path with valid token' do
|
32
|
+
token = JWT.encode(
|
33
|
+
{ email: 'admin@saas.com', scope: 'admin' },
|
34
|
+
jwt_hmac_secret,
|
35
|
+
'HS256',
|
36
|
+
)
|
37
|
+
|
38
|
+
get('/admin', { admin_token: token })
|
39
|
+
|
40
|
+
expect(last_response).to be_redirect
|
41
|
+
follow_redirect!
|
42
|
+
expect(last_request.url).to match('/admin')
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'renders the admin page for a valid session token' do
|
46
|
+
token = JWT.encode(
|
47
|
+
{ email: 'admin@saas.com', scope: 'admin' },
|
48
|
+
jwt_hmac_secret,
|
49
|
+
'HS256',
|
50
|
+
)
|
51
|
+
|
52
|
+
get('/admin', {}, 'rack.session' => { admin_token: token })
|
53
|
+
|
54
|
+
expect(last_response).to be_ok
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|