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