osso 0.0.5.pre.delta → 0.0.5.pre.epsilon
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/osso.rb +2 -0
- data/lib/osso/error/error.rb +10 -0
- data/lib/osso/error/missing_saml_attribute_error.rb +21 -0
- data/lib/osso/error/oauth_error.rb +29 -0
- data/lib/osso/error/saml_config_error.rb +13 -0
- data/lib/osso/lib/saml_handler.rb +85 -0
- data/lib/osso/routes/auth.rb +18 -41
- data/lib/osso/routes/oauth.rb +30 -14
- data/lib/osso/version.rb +1 -1
- data/spec/lib/saml_handler_spec.rb +1 -0
- data/spec/routes/auth_spec.rb +35 -8
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82bd613a1b8d5eb4f9c549d9d855c629698a120d2ee99bf3a52f6911fa4d080a
|
4
|
+
data.tar.gz: a67aec622f76bcc42cc16065d11a795c09893caa1115759b5739fa384108f6f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2a1ba5df34d2e175ed7a3ab7e61dcb573b5eb9a636753f7159860f817e6b61515b9350b63a1ac1ffece994f27a7f17d61d2de05e5f6a0468168cfb7a268f879
|
7
|
+
data.tar.gz: 475eba77f824e32701ff9eae95bef4663f2e0de8d43c9d542846f6ecaec55edb1242251cb4ed50b9cb1db627252293cf406a633cc8c0c63fb7f419e10dc95e3c
|
data/Gemfile.lock
CHANGED
data/lib/osso.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Osso
|
4
|
+
require_relative 'osso/error/error'
|
4
5
|
require_relative 'osso/helpers/helpers'
|
5
6
|
require_relative 'osso/lib/app_config'
|
6
7
|
require_relative 'osso/lib/oauth2_token'
|
7
8
|
require_relative 'osso/lib/route_map'
|
9
|
+
require_relative 'osso/lib/saml_handler'
|
8
10
|
require_relative 'osso/models/models'
|
9
11
|
require_relative 'osso/routes/routes'
|
10
12
|
require_relative 'osso/graphql/schema'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Osso
|
4
|
+
module Error
|
5
|
+
class MissingSamlAttributeError < StandardError; end
|
6
|
+
|
7
|
+
class MissingSamlEmailAttributeError < MissingSamlAttributeError
|
8
|
+
def message
|
9
|
+
'SAML response does not include the attribute `email`. ' \
|
10
|
+
"Review the setup guide and check the attributes you're sending from your Identity Provider."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class MissingSamlIdAttributeError < MissingSamlAttributeError
|
15
|
+
def message
|
16
|
+
'SAML response does not include the attribute `id` or `idp_id`.' \
|
17
|
+
"Review the setup guide and check the attributes you're sending from your Identity Provider."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Osso
|
4
|
+
module Error
|
5
|
+
class OAuthError < StandardError; end
|
6
|
+
|
7
|
+
class NoAccountForOAuthClientError < OAuthError
|
8
|
+
def message
|
9
|
+
'No customer account exists for the requested domain and OAuth client pair.' \
|
10
|
+
"Review our OAuth documentation, and check you're using the correct OAuth client identifier"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class InvalidOAuthClientIdentifier < MissingSamlAttributeError
|
15
|
+
def message
|
16
|
+
'No OAuth client exists for the requested OAuth client identifier.' \
|
17
|
+
"Review our OAuth documentation, and check you're using the correct OAuth client identifier"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class InvalidRedirectUri < MissingSamlAttributeError
|
22
|
+
def message
|
23
|
+
'Invalid Redirect URI for the requested OAuth client identifier.' \
|
24
|
+
"Review our OAuth documentation, check you're using the correct OAuth client identifier " \
|
25
|
+
'and confirm your Redirect URI allow list.'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Osso
|
4
|
+
module Error
|
5
|
+
class SamlConfigError < StandardError; end
|
6
|
+
|
7
|
+
class InvalidACSURLError < SamlConfigError
|
8
|
+
def message
|
9
|
+
'The ACS URL specfied in your Identity Provider configuration is malformed.'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Osso
|
4
|
+
class SamlHandler
|
5
|
+
attr_accessor :session, :provider, :attributes
|
6
|
+
|
7
|
+
def self.perform(**attrs)
|
8
|
+
new(attrs).perform
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(auth_hash:, provider_id:, session:)
|
12
|
+
find_provider(provider_id)
|
13
|
+
@attributes = auth_hash&.extra&.response_object&.attributes
|
14
|
+
@session = session
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
validate_attributes
|
19
|
+
provider.active!
|
20
|
+
redirect_uri
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def find_provider(id)
|
26
|
+
@provider ||= Models::IdentityProvider.find(id)
|
27
|
+
rescue ActiveRecord::RecordNotFound
|
28
|
+
raise Osso::Error::InvalidACSURLError
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_attributes
|
32
|
+
raise Osso::Error::MissingSamlIdAttributeError unless id_attribute
|
33
|
+
raise Osso::Error::MissingSamlEmailAttributeError unless email_attribute
|
34
|
+
end
|
35
|
+
|
36
|
+
def id_attribute
|
37
|
+
@id_attribute ||= attributes[:id] || attributes[:idp_id]
|
38
|
+
end
|
39
|
+
|
40
|
+
def email_attribute
|
41
|
+
attributes[:email]
|
42
|
+
end
|
43
|
+
|
44
|
+
def user
|
45
|
+
@user ||= Models::User.where(
|
46
|
+
email: email_attribute,
|
47
|
+
idp_id: id_attribute,
|
48
|
+
).first_or_create! do |new_user|
|
49
|
+
new_user.enterprise_account_id = provider.enterprise_account_id
|
50
|
+
new_user.identity_provider_id = provider.id
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def authorization_code
|
55
|
+
@authorization_code ||= user.authorization_codes.create!(
|
56
|
+
oauth_client: provider.oauth_client,
|
57
|
+
redirect_uri: redirect_uri_base,
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def redirect_uri
|
62
|
+
redirect_uri_base + redirect_uri_querystring
|
63
|
+
end
|
64
|
+
|
65
|
+
def redirect_uri_base
|
66
|
+
return provider.oauth_client.primary_redirect_uri.uri if valid_idp_initiated_flow
|
67
|
+
|
68
|
+
session[:osso_oauth_redirect_uri]
|
69
|
+
end
|
70
|
+
|
71
|
+
def redirect_uri_querystring
|
72
|
+
"?code=#{CGI.escape(authorization_code.token)}&state=#{provider_state}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def provider_state
|
76
|
+
return 'IDP_INITIATED' if valid_idp_initiated_flow
|
77
|
+
|
78
|
+
session.delete(:osso_oauth_state)
|
79
|
+
end
|
80
|
+
|
81
|
+
def valid_idp_initiated_flow
|
82
|
+
!session[:osso_oauth_redirect_uri] && !session[:osso_oauth_state]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/osso/routes/auth.rb
CHANGED
@@ -27,9 +27,16 @@ module Osso
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
|
30
|
+
OmniAuth.config.on_failure = proc do |env|
|
31
|
+
OmniAuth::FailureEndpoint.new(env).redirect_to_failure
|
32
|
+
end
|
33
|
+
|
34
|
+
error do
|
35
|
+
erb :error
|
36
|
+
end
|
37
|
+
|
38
|
+
namespace '/auth' do
|
31
39
|
get '/failure' do
|
32
|
-
@error = params[:message]
|
33
40
|
erb :error
|
34
41
|
end
|
35
42
|
# Enterprise users are sent here after authenticating against
|
@@ -37,47 +44,17 @@ module Osso
|
|
37
44
|
# and then create an authorization code for that user. The user
|
38
45
|
# is redirected back to your application with this code
|
39
46
|
# as a URL query param, which you then exchange for an access token.
|
40
|
-
post '/saml/:
|
41
|
-
|
42
|
-
|
43
|
-
|
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] || attributes[:idp_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,
|
47
|
+
post '/saml/:provider_id/callback' do
|
48
|
+
redirect_uri = SamlHandler.perform(
|
49
|
+
auth_hash: env['omniauth.auth'],
|
50
|
+
provider_id: params[:provider_id],
|
51
|
+
session: session,
|
61
52
|
)
|
62
|
-
provider.active!
|
63
|
-
|
64
|
-
redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{provider_state}")
|
65
|
-
end
|
66
|
-
|
67
|
-
def redirect_uri
|
68
|
-
return @oauth_client.primary_redirect_uri.uri if valid_idp_initiated_flow
|
69
|
-
|
70
|
-
session[:osso_oauth_redirect_uri]
|
71
|
-
end
|
72
53
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
def valid_idp_initiated_flow
|
80
|
-
!session[:osso_oauth_redirect_uri] && !session[:osso_oauth_state]
|
54
|
+
redirect(redirect_uri)
|
55
|
+
rescue Osso::Error::InvalidACSURLError => e
|
56
|
+
@error = e
|
57
|
+
erb :error
|
81
58
|
end
|
82
59
|
end
|
83
60
|
end
|
data/lib/osso/routes/oauth.rb
CHANGED
@@ -16,28 +16,18 @@ module Osso
|
|
16
16
|
# Once they complete IdP login, they will be returned to the
|
17
17
|
# redirect_uri with an authorization code parameter.
|
18
18
|
get '/authorize' do
|
19
|
-
|
20
|
-
|
21
|
-
session[:osso_oauth_redirect_uri] = req.verify_redirect_uri!(client.redirect_uri_values)
|
22
|
-
session[:osso_oauth_state] = params[:state]
|
23
|
-
end.call(env)
|
19
|
+
client = find_client(params[:client_id])
|
20
|
+
enterprise = find_account(domain: params[:domain], client_id: client.id)
|
24
21
|
|
25
|
-
|
26
|
-
includes(:identity_providers).
|
27
|
-
find_by!(domain: params[:domain])
|
22
|
+
validate_oauth_request(env)
|
28
23
|
|
29
24
|
redirect "/auth/saml/#{enterprise.provider.id}" if enterprise.single_provider?
|
30
25
|
|
31
26
|
@providers = enterprise.identity_providers
|
32
27
|
erb :multiple_providers
|
33
28
|
|
34
|
-
rescue
|
35
|
-
@error = e
|
36
|
-
erb :error
|
37
|
-
rescue ActiveRecord::RecordNotFound => e
|
29
|
+
rescue Osso::Error::OAuthError => e
|
38
30
|
@error = e
|
39
|
-
@error = 'No OAuth Client exists for the provided client_id' if e.model == 'Osso::Models::OauthClient'
|
40
|
-
@error = "No Customer exists with the domain #{params[:domain]}" if e.model == 'Osso::Models::EnterpriseAccount'
|
41
31
|
erb :error
|
42
32
|
end
|
43
33
|
|
@@ -66,5 +56,31 @@ module Osso
|
|
66
56
|
user
|
67
57
|
end
|
68
58
|
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def find_account(domain:, client_id:)
|
63
|
+
Models::EnterpriseAccount.
|
64
|
+
includes(:identity_providers).
|
65
|
+
find_by!(domain: domain, oauth_client_id: client_id)
|
66
|
+
rescue ActiveRecord::RecordNotFound
|
67
|
+
raise Osso::Error::NoAccountForOAuthClientError
|
68
|
+
end
|
69
|
+
|
70
|
+
def find_client(identifier)
|
71
|
+
@client ||= Models::OauthClient.find_by!(identifier: identifier)
|
72
|
+
rescue ActiveRecord::RecordNotFound
|
73
|
+
raise Osso::Error::InvalidOAuthClientIdentifier
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_oauth_request(env)
|
77
|
+
Rack::OAuth2::Server::Authorize.new do |req, _res|
|
78
|
+
client = find_client(req[:client_id])
|
79
|
+
session[:osso_oauth_redirect_uri] = req.verify_redirect_uri!(client.redirect_uri_values)
|
80
|
+
session[:osso_oauth_state] = params[:state]
|
81
|
+
end.call(env)
|
82
|
+
rescue Rack::OAuth2::Server::Authorize::BadRequest
|
83
|
+
raise Osso::Error::InvalidRedirectUri
|
84
|
+
end
|
69
85
|
end
|
70
86
|
end
|
data/lib/osso/version.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/spec/routes/auth_spec.rb
CHANGED
@@ -43,7 +43,6 @@ describe Osso::Auth do
|
|
43
43
|
nil,
|
44
44
|
{
|
45
45
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
46
|
-
'identity_provider' => okta_provider,
|
47
46
|
},
|
48
47
|
)
|
49
48
|
end.to change { Osso::Models::User.count }.by(1)
|
@@ -58,7 +57,6 @@ describe Osso::Auth do
|
|
58
57
|
nil,
|
59
58
|
{
|
60
59
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
61
|
-
'identity_provider' => okta_provider,
|
62
60
|
},
|
63
61
|
)
|
64
62
|
end.to change { Osso::Models::AuthorizationCode.count }.by(1)
|
@@ -73,7 +71,6 @@ describe Osso::Auth do
|
|
73
71
|
nil,
|
74
72
|
{
|
75
73
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
76
|
-
'identity_provider' => okta_provider,
|
77
74
|
},
|
78
75
|
)
|
79
76
|
expect(last_response).to be_redirect
|
@@ -99,7 +96,6 @@ describe Osso::Auth do
|
|
99
96
|
nil,
|
100
97
|
{
|
101
98
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
102
|
-
'identity_provider' => okta_provider,
|
103
99
|
},
|
104
100
|
)
|
105
101
|
end.to_not(change { Osso::Models::User.count })
|
@@ -110,7 +106,6 @@ describe Osso::Auth do
|
|
110
106
|
nil,
|
111
107
|
{
|
112
108
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
113
|
-
'identity_provider' => okta_provider,
|
114
109
|
},
|
115
110
|
)
|
116
111
|
expect(okta_provider.reload.status).to eq('ACTIVE')
|
@@ -132,7 +127,6 @@ describe Osso::Auth do
|
|
132
127
|
nil,
|
133
128
|
{
|
134
129
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
135
|
-
'identity_provider' => azure_provider,
|
136
130
|
},
|
137
131
|
)
|
138
132
|
end.to change { Osso::Models::User.count }.by(1)
|
@@ -146,7 +140,6 @@ describe Osso::Auth do
|
|
146
140
|
nil,
|
147
141
|
{
|
148
142
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
149
|
-
'identity_provider' => azure_provider,
|
150
143
|
},
|
151
144
|
)
|
152
145
|
|
@@ -170,7 +163,6 @@ describe Osso::Auth do
|
|
170
163
|
nil,
|
171
164
|
{
|
172
165
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
173
|
-
'identity_provider' => azure_provider,
|
174
166
|
},
|
175
167
|
)
|
176
168
|
end.to_not(change { Osso::Models::User.count })
|
@@ -178,4 +170,39 @@ describe Osso::Auth do
|
|
178
170
|
end
|
179
171
|
end
|
180
172
|
end
|
173
|
+
|
174
|
+
context 'with an invalid SAML response' do
|
175
|
+
describe 'post /auth/saml/:uuid/callback' do
|
176
|
+
let!(:enterprise) { create(:enterprise_with_azure) }
|
177
|
+
let!(:azure_provider) { enterprise.provider }
|
178
|
+
|
179
|
+
it 'raises an error when email is missing' do
|
180
|
+
mock_saml_omniauth(email: nil, id: SecureRandom.uuid)
|
181
|
+
|
182
|
+
expect do
|
183
|
+
post(
|
184
|
+
"/auth/saml/#{azure_provider.id}/callback",
|
185
|
+
nil,
|
186
|
+
{
|
187
|
+
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
188
|
+
},
|
189
|
+
)
|
190
|
+
end.to raise_error(Osso::Error::MissingSamlEmailAttributeError)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'raises an error when id is missing' do
|
194
|
+
mock_saml_omniauth(email: Faker::Internet.email, id: nil)
|
195
|
+
|
196
|
+
expect do
|
197
|
+
post(
|
198
|
+
"/auth/saml/#{azure_provider.id}/callback",
|
199
|
+
nil,
|
200
|
+
{
|
201
|
+
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
202
|
+
},
|
203
|
+
)
|
204
|
+
end.to raise_error(Osso::Error::MissingSamlIdAttributeError)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
181
208
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: osso
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.5.pre.
|
4
|
+
version: 0.0.5.pre.epsilon
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Bauch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-09-
|
11
|
+
date: 2020-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -273,6 +273,10 @@ files:
|
|
273
273
|
- lib/osso/db/migrate/20200826201852_create_app_config.rb
|
274
274
|
- lib/osso/db/migrate/20200913154919_add_one_login_to_identity_provider_service_enum.rb
|
275
275
|
- lib/osso/db/migrate/20200916125543_add_google_to_identity_provider_service_enum.rb
|
276
|
+
- lib/osso/error/error.rb
|
277
|
+
- lib/osso/error/missing_saml_attribute_error.rb
|
278
|
+
- lib/osso/error/oauth_error.rb
|
279
|
+
- lib/osso/error/saml_config_error.rb
|
276
280
|
- lib/osso/graphql/.DS_Store
|
277
281
|
- lib/osso/graphql/mutation.rb
|
278
282
|
- lib/osso/graphql/mutations.rb
|
@@ -313,6 +317,7 @@ files:
|
|
313
317
|
- lib/osso/lib/app_config.rb
|
314
318
|
- lib/osso/lib/oauth2_token.rb
|
315
319
|
- lib/osso/lib/route_map.rb
|
320
|
+
- lib/osso/lib/saml_handler.rb
|
316
321
|
- lib/osso/models/access_token.rb
|
317
322
|
- lib/osso/models/app_config.rb
|
318
323
|
- lib/osso/models/authorization_code.rb
|
@@ -347,6 +352,7 @@ files:
|
|
347
352
|
- spec/graphql/query/identity_provider_spec.rb
|
348
353
|
- spec/graphql/query/oauth_clients_spec.rb
|
349
354
|
- spec/helpers/auth_spec.rb
|
355
|
+
- spec/lib/saml_handler_spec.rb
|
350
356
|
- spec/models/identity_provider_spec.rb
|
351
357
|
- spec/routes/admin_spec.rb
|
352
358
|
- spec/routes/app_spec.rb
|