osso 0.0.5.pre.eta → 0.0.5.pre.gamma
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 +24 -22
- data/lib/osso/graphql/types/identity_provider_status.rb +4 -4
- data/lib/osso/helpers/auth.rb +1 -1
- data/lib/osso/lib/route_map.rb +0 -1
- data/lib/osso/models/enterprise_account.rb +1 -1
- data/lib/osso/models/identity_provider.rb +5 -5
- data/lib/osso/routes/admin.rb +6 -6
- data/lib/osso/routes/auth.rb +44 -39
- data/lib/osso/routes/oauth.rb +16 -33
- data/lib/osso/version.rb +1 -1
- data/lib/osso.rb +0 -2
- data/spec/factories/enterprise_account.rb +4 -8
- data/spec/helpers/auth_spec.rb +2 -174
- data/spec/routes/auth_spec.rb +10 -41
- data/spec/support/views/error.erb +0 -1
- metadata +2 -9
- data/lib/osso/error/account_configuration_error.rb +0 -19
- data/lib/osso/error/error.rb +0 -16
- data/lib/osso/error/missing_saml_attribute_error.rb +0 -21
- data/lib/osso/error/oauth_error.rb +0 -43
- data/lib/osso/error/saml_config_error.rb +0 -17
- data/lib/osso/lib/saml_handler.rb +0 -85
- data/spec/lib/saml_handler_spec.rb +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8349ed4f80afe46d642e3e1e7aaa9b9d0f90ece6e0fd6b8e0db416f74cc54f1
|
|
4
|
+
data.tar.gz: 9aa776d8e26b04570e4123baf232404c3e9059c66bcb1fe39f2ac337c59e7edd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aa38f696f541aa36893252c26aa1ffea8b6ac33b05b3bb130ecf7b1b71094042a7546984d67857ba8002c47744c47a1c813e7d972f38068be6c50f35a11aa9b7
|
|
7
|
+
data.tar.gz: 515be1591f3d5ce752057483cfa8e7d94d1665a7534fe1112c5c607f7ec2417ff650367826d7eec71d64086d735994c4f1b7ac322756df103adbdb6c4958bec2
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
osso (0.0.5.pre.
|
|
4
|
+
osso (0.0.5.pre.gamma)
|
|
5
5
|
activesupport (>= 6.0.3.2)
|
|
6
6
|
graphql
|
|
7
7
|
jwt
|
|
@@ -18,12 +18,12 @@ PATH
|
|
|
18
18
|
GEM
|
|
19
19
|
remote: https://rubygems.org/
|
|
20
20
|
specs:
|
|
21
|
-
activemodel (6.0.3.
|
|
22
|
-
activesupport (= 6.0.3.
|
|
23
|
-
activerecord (6.0.3.
|
|
24
|
-
activemodel (= 6.0.3.
|
|
25
|
-
activesupport (= 6.0.3.
|
|
26
|
-
activesupport (6.0.3.
|
|
21
|
+
activemodel (6.0.3.2)
|
|
22
|
+
activesupport (= 6.0.3.2)
|
|
23
|
+
activerecord (6.0.3.2)
|
|
24
|
+
activemodel (= 6.0.3.2)
|
|
25
|
+
activesupport (= 6.0.3.2)
|
|
26
|
+
activesupport (6.0.3.2)
|
|
27
27
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
28
28
|
i18n (>= 0.7, < 2)
|
|
29
29
|
minitest (~> 5.1)
|
|
@@ -39,23 +39,24 @@ GEM
|
|
|
39
39
|
attr_required (1.0.1)
|
|
40
40
|
bindata (2.4.8)
|
|
41
41
|
coderay (1.1.3)
|
|
42
|
-
concurrent-ruby (1.1.
|
|
43
|
-
crack (0.4.
|
|
42
|
+
concurrent-ruby (1.1.6)
|
|
43
|
+
crack (0.4.3)
|
|
44
|
+
safe_yaml (~> 1.0.0)
|
|
44
45
|
database_cleaner (1.8.5)
|
|
45
46
|
database_cleaner-active_record (1.8.0)
|
|
46
47
|
activerecord
|
|
47
48
|
database_cleaner (~> 1.8.0)
|
|
48
49
|
diff-lcs (1.4.4)
|
|
49
50
|
docile (1.3.2)
|
|
50
|
-
factory_bot (6.
|
|
51
|
+
factory_bot (6.0.2)
|
|
51
52
|
activesupport (>= 5.0.0)
|
|
52
|
-
faker (2.
|
|
53
|
+
faker (2.13.0)
|
|
53
54
|
i18n (>= 1.6, < 2)
|
|
54
55
|
graphql (1.11.4)
|
|
55
56
|
hashdiff (1.0.1)
|
|
56
57
|
hashie (4.1.0)
|
|
57
58
|
httpclient (2.8.3)
|
|
58
|
-
i18n (1.8.
|
|
59
|
+
i18n (1.8.3)
|
|
59
60
|
concurrent-ruby (~> 1.0)
|
|
60
61
|
json (2.3.1)
|
|
61
62
|
json-jwt (1.13.0)
|
|
@@ -65,7 +66,7 @@ GEM
|
|
|
65
66
|
jwt (2.2.2)
|
|
66
67
|
method_source (1.0.0)
|
|
67
68
|
mini_portile2 (2.4.0)
|
|
68
|
-
minitest (5.14.
|
|
69
|
+
minitest (5.14.1)
|
|
69
70
|
multi_json (1.15.0)
|
|
70
71
|
mustermann (1.1.1)
|
|
71
72
|
ruby2_keywords (~> 0.0.1)
|
|
@@ -86,7 +87,7 @@ GEM
|
|
|
86
87
|
pry (0.13.1)
|
|
87
88
|
coderay (~> 1.1)
|
|
88
89
|
method_source (~> 1.0)
|
|
89
|
-
public_suffix (4.0.
|
|
90
|
+
public_suffix (4.0.5)
|
|
90
91
|
rack (2.2.3)
|
|
91
92
|
rack-contrib (2.2.0)
|
|
92
93
|
rack (~> 2.0)
|
|
@@ -102,7 +103,7 @@ GEM
|
|
|
102
103
|
rack (>= 1.0, < 3)
|
|
103
104
|
rainbow (3.0.0)
|
|
104
105
|
rake (13.0.1)
|
|
105
|
-
regexp_parser (1.
|
|
106
|
+
regexp_parser (1.7.1)
|
|
106
107
|
rexml (3.2.4)
|
|
107
108
|
rspec (3.9.0)
|
|
108
109
|
rspec-core (~> 3.9.0)
|
|
@@ -117,21 +118,22 @@ GEM
|
|
|
117
118
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
118
119
|
rspec-support (~> 3.9.0)
|
|
119
120
|
rspec-support (3.9.3)
|
|
120
|
-
rubocop (0.
|
|
121
|
+
rubocop (0.86.0)
|
|
121
122
|
parallel (~> 1.10)
|
|
122
|
-
parser (>= 2.7.
|
|
123
|
+
parser (>= 2.7.0.1)
|
|
123
124
|
rainbow (>= 2.2.2, < 4.0)
|
|
124
125
|
regexp_parser (>= 1.7)
|
|
125
126
|
rexml
|
|
126
|
-
rubocop-ast (>= 0.
|
|
127
|
+
rubocop-ast (>= 0.0.3, < 1.0)
|
|
127
128
|
ruby-progressbar (~> 1.7)
|
|
128
129
|
unicode-display_width (>= 1.4.0, < 2.0)
|
|
129
|
-
rubocop-ast (0.
|
|
130
|
-
parser (>= 2.7.1
|
|
130
|
+
rubocop-ast (0.1.0)
|
|
131
|
+
parser (>= 2.7.0.1)
|
|
131
132
|
ruby-progressbar (1.10.1)
|
|
132
133
|
ruby-saml (1.11.0)
|
|
133
134
|
nokogiri (>= 1.5.10)
|
|
134
135
|
ruby2_keywords (0.0.2)
|
|
136
|
+
safe_yaml (1.0.5)
|
|
135
137
|
simplecov (0.17.0)
|
|
136
138
|
docile (~> 1.1)
|
|
137
139
|
json (>= 1.8, < 3)
|
|
@@ -156,11 +158,11 @@ GEM
|
|
|
156
158
|
tzinfo (1.2.7)
|
|
157
159
|
thread_safe (~> 0.1)
|
|
158
160
|
unicode-display_width (1.7.0)
|
|
159
|
-
webmock (3.
|
|
161
|
+
webmock (3.8.3)
|
|
160
162
|
addressable (>= 2.3.6)
|
|
161
163
|
crack (>= 0.3.2)
|
|
162
164
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
163
|
-
zeitwerk (2.
|
|
165
|
+
zeitwerk (2.3.1)
|
|
164
166
|
|
|
165
167
|
PLATFORMS
|
|
166
168
|
ruby
|
|
@@ -4,10 +4,10 @@ module Osso
|
|
|
4
4
|
module GraphQL
|
|
5
5
|
module Types
|
|
6
6
|
class IdentityProviderStatus < BaseEnum
|
|
7
|
-
value('Pending', value: '
|
|
8
|
-
value('Configured', value: '
|
|
9
|
-
value('Active', value: '
|
|
10
|
-
value('Error', value: '
|
|
7
|
+
value('Pending', value: 'PENDING')
|
|
8
|
+
value('Configured', value: 'CONFIGURED')
|
|
9
|
+
value('Active', value: 'ACTIVE')
|
|
10
|
+
value('Error', value: 'ERROR')
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
end
|
data/lib/osso/helpers/auth.rb
CHANGED
data/lib/osso/lib/route_map.rb
CHANGED
|
@@ -10,8 +10,6 @@ module Osso
|
|
|
10
10
|
before_save :set_status
|
|
11
11
|
validate :sso_cert_valid
|
|
12
12
|
|
|
13
|
-
enum status: { pending: "PENDING", configured: 'CONFIGURED', active: "ACTIVE", error: "ERROR"}
|
|
14
|
-
|
|
15
13
|
PEM_HEADER = "-----BEGIN CERTIFICATE-----\n"
|
|
16
14
|
PEM_FOOTER = "\n-----END CERTIFICATE-----"
|
|
17
15
|
|
|
@@ -41,15 +39,17 @@ module Osso
|
|
|
41
39
|
alias acs_url assertion_consumer_service_url
|
|
42
40
|
|
|
43
41
|
def set_status
|
|
44
|
-
|
|
42
|
+
return if status != 'PENDING'
|
|
43
|
+
|
|
44
|
+
self.status = 'CONFIGURED' if sso_url && sso_cert
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def active!
|
|
48
|
-
update(status: '
|
|
48
|
+
update(status: 'ACTIVE')
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def error!
|
|
52
|
-
update(status: '
|
|
52
|
+
update(status: 'ERROR')
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def root_url
|
data/lib/osso/routes/admin.rb
CHANGED
|
@@ -16,37 +16,37 @@ module Osso
|
|
|
16
16
|
get '/login' do
|
|
17
17
|
token_protected!
|
|
18
18
|
|
|
19
|
-
erb :admin
|
|
19
|
+
erb :admin
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
get '' do
|
|
23
23
|
internal_protected!
|
|
24
24
|
|
|
25
|
-
erb :admin
|
|
25
|
+
erb :admin
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
get '/enterprise' do
|
|
29
29
|
token_protected!
|
|
30
30
|
|
|
31
|
-
erb :admin
|
|
31
|
+
erb :admin
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
get '/enterprise/:domain' do
|
|
35
35
|
enterprise_protected!(params[:domain])
|
|
36
36
|
|
|
37
|
-
erb :admin
|
|
37
|
+
erb :admin
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
get '/config' do
|
|
41
41
|
admin_protected!
|
|
42
42
|
|
|
43
|
-
erb :admin
|
|
43
|
+
erb :admin
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
get '/config/:id' do
|
|
47
47
|
admin_protected!
|
|
48
48
|
|
|
49
|
-
erb :admin
|
|
49
|
+
erb :admin
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
end
|
data/lib/osso/routes/auth.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Osso
|
|
|
13
13
|
UUID_REGEXP =
|
|
14
14
|
/[0-9a-f]{8}-[0-9a-f]{3,4}-[0-9a-f]{4}-[0-9a-f]{3,4}-[0-9a-f]{12}/.
|
|
15
15
|
freeze
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
use OmniAuth::Builder do
|
|
18
18
|
OmniAuth::MultiProvider.register(
|
|
19
19
|
self,
|
|
@@ -22,39 +22,14 @@ module Osso
|
|
|
22
22
|
path_prefix: '/auth/saml',
|
|
23
23
|
callback_suffix: 'callback',
|
|
24
24
|
) do |identity_provider_id, _env|
|
|
25
|
-
Models::IdentityProvider.
|
|
26
|
-
not_pending.
|
|
27
|
-
find(identity_provider_id).
|
|
25
|
+
Models::IdentityProvider.find(identity_provider_id).
|
|
28
26
|
saml_options
|
|
29
|
-
rescue
|
|
30
|
-
{}
|
|
31
27
|
end
|
|
32
28
|
end
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
OmniAuth::FailureEndpoint.new(env).redirect_to_failure
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
namespace '/auth' do
|
|
30
|
+
namespace '/auth' do # rubocop:disable Metrics/BlockLength
|
|
39
31
|
get '/failure' do
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# confirmed:
|
|
43
|
-
# - a valid but wrong cert will throw here
|
|
44
|
-
# (OneLogin::RubySaml::ValidationError, Fingerprint mismatch)
|
|
45
|
-
# but an _invalid_ cert is not caught. we do validate certs on
|
|
46
|
-
# configuration, so this may be ok
|
|
47
|
-
#
|
|
48
|
-
# - a valid but wrong ACS URL will throw here. the urls
|
|
49
|
-
# are pretty complex, but it has come up
|
|
50
|
-
#
|
|
51
|
-
# - specifying the wrong "recipient" in your IDP. Only OL so far
|
|
52
|
-
# (OneLogin::RubySaml::ValidationError, The response was received
|
|
53
|
-
# at vcardme.com instead of
|
|
54
|
-
# http://localhost:9292/auth/saml/e54a9a92-b4b5-4ea5-b0e3-b1423eb20b76/callback)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@error = Osso::Error::SamlConfigError.new
|
|
32
|
+
@error = params[:message]
|
|
58
33
|
erb :error
|
|
59
34
|
end
|
|
60
35
|
# Enterprise users are sent here after authenticating against
|
|
@@ -62,18 +37,48 @@ module Osso
|
|
|
62
37
|
# and then create an authorization code for that user. The user
|
|
63
38
|
# is redirected back to your application with this code
|
|
64
39
|
# as a URL query param, which you then exchange for an access token.
|
|
65
|
-
post '/saml/:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
post '/saml/:id/callback' do
|
|
41
|
+
provider = Models::IdentityProvider.find(params[:id])
|
|
42
|
+
@oauth_client = provider.oauth_client
|
|
43
|
+
|
|
44
|
+
# TODO: PORC for validating attributes
|
|
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,
|
|
70
61
|
)
|
|
62
|
+
provider.active!
|
|
71
63
|
|
|
72
|
-
redirect(redirect_uri)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
|
|
73
|
+
def provider_state
|
|
74
|
+
return @provider_state = 'IDP_INITIATED' if valid_idp_initiated_flow
|
|
75
|
+
|
|
76
|
+
session.delete(:osso_oauth_state)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def valid_idp_initiated_flow
|
|
80
|
+
!session[:osso_oauth_redirect_uri] && !session[:osso_oauth_state]
|
|
81
|
+
end
|
|
77
82
|
end
|
|
78
83
|
end
|
|
79
84
|
end
|
data/lib/osso/routes/oauth.rb
CHANGED
|
@@ -16,19 +16,28 @@ 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
|
-
|
|
19
|
+
Rack::OAuth2::Server::Authorize.new do |req, _res|
|
|
20
|
+
client = Models::OauthClient.find_by!(identifier: req.client_id)
|
|
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)
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
enterprise = Models::EnterpriseAccount.
|
|
26
|
+
includes(:identity_providers).
|
|
27
|
+
find_by!(domain: params[:domain])
|
|
23
28
|
|
|
24
29
|
redirect "/auth/saml/#{enterprise.provider.id}" if enterprise.single_provider?
|
|
25
30
|
|
|
26
|
-
@providers = enterprise.identity_providers
|
|
27
|
-
erb :multiple_providers
|
|
31
|
+
@providers = enterprise.identity_providers
|
|
32
|
+
erb :multiple_providers
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
rescue Rack::OAuth2::Server::Authorize::BadRequest => e
|
|
35
|
+
@error = e
|
|
36
|
+
erb :error
|
|
37
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
31
38
|
@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'
|
|
32
41
|
erb :error
|
|
33
42
|
end
|
|
34
43
|
|
|
@@ -57,31 +66,5 @@ module Osso
|
|
|
57
66
|
user
|
|
58
67
|
end
|
|
59
68
|
end
|
|
60
|
-
|
|
61
|
-
private
|
|
62
|
-
|
|
63
|
-
def find_account(domain:, client_id:)
|
|
64
|
-
Models::EnterpriseAccount.
|
|
65
|
-
includes(:identity_providers).
|
|
66
|
-
find_by!(domain: domain, oauth_client_id: client_id)
|
|
67
|
-
rescue ActiveRecord::RecordNotFound
|
|
68
|
-
raise Osso::Error::NoAccountForOAuthClientError.new(domain: params[:domain])
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def find_client(identifier)
|
|
72
|
-
@client ||= Models::OauthClient.find_by!(identifier: identifier)
|
|
73
|
-
rescue ActiveRecord::RecordNotFound
|
|
74
|
-
raise Osso::Error::InvalidOAuthClientIdentifier
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def validate_oauth_request(env)
|
|
78
|
-
Rack::OAuth2::Server::Authorize.new do |req, _res|
|
|
79
|
-
client = find_client(req[:client_id])
|
|
80
|
-
session[:osso_oauth_redirect_uri] = req.verify_redirect_uri!(client.redirect_uri_values)
|
|
81
|
-
session[:osso_oauth_state] = params[:state]
|
|
82
|
-
end.call(env)
|
|
83
|
-
rescue Rack::OAuth2::Server::Authorize::BadRequest
|
|
84
|
-
raise Osso::Error::InvalidRedirectUri.new(redirect_uri: params[:redirect_uri])
|
|
85
|
-
end
|
|
86
69
|
end
|
|
87
70
|
end
|
data/lib/osso/version.rb
CHANGED
data/lib/osso.rb
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Osso
|
|
4
|
-
require_relative 'osso/error/error'
|
|
5
4
|
require_relative 'osso/helpers/helpers'
|
|
6
5
|
require_relative 'osso/lib/app_config'
|
|
7
6
|
require_relative 'osso/lib/oauth2_token'
|
|
8
7
|
require_relative 'osso/lib/route_map'
|
|
9
|
-
require_relative 'osso/lib/saml_handler'
|
|
10
8
|
require_relative 'osso/models/models'
|
|
11
9
|
require_relative 'osso/routes/routes'
|
|
12
10
|
require_relative 'osso/graphql/schema'
|
|
@@ -11,8 +11,7 @@ FactoryBot.define do
|
|
|
11
11
|
factory :enterprise_with_okta, parent: :enterprise_account do
|
|
12
12
|
after :create do |enterprise|
|
|
13
13
|
create(
|
|
14
|
-
:
|
|
15
|
-
service: 'OKTA',
|
|
14
|
+
:okta_identity_provider,
|
|
16
15
|
domain: enterprise.domain,
|
|
17
16
|
enterprise_account_id: enterprise.id,
|
|
18
17
|
)
|
|
@@ -22,8 +21,7 @@ FactoryBot.define do
|
|
|
22
21
|
factory :enterprise_with_azure, parent: :enterprise_account do
|
|
23
22
|
after :create do |enterprise|
|
|
24
23
|
create(
|
|
25
|
-
:
|
|
26
|
-
service: 'AZURE',
|
|
24
|
+
:azure_identity_provider,
|
|
27
25
|
domain: enterprise.domain,
|
|
28
26
|
enterprise_account_id: enterprise.id,
|
|
29
27
|
)
|
|
@@ -33,15 +31,13 @@ FactoryBot.define do
|
|
|
33
31
|
factory :enterprise_with_multiple_providers, parent: :enterprise_account do
|
|
34
32
|
after :create do |enterprise|
|
|
35
33
|
create(
|
|
36
|
-
:
|
|
37
|
-
service: 'OKTA',
|
|
34
|
+
:okta_identity_provider,
|
|
38
35
|
domain: enterprise.domain,
|
|
39
36
|
enterprise_account_id: enterprise.id,
|
|
40
37
|
)
|
|
41
38
|
|
|
42
39
|
create(
|
|
43
|
-
:
|
|
44
|
-
service: 'AZURE',
|
|
40
|
+
:azure_identity_provider,
|
|
45
41
|
domain: enterprise.domain,
|
|
46
42
|
enterprise_account_id: enterprise.id,
|
|
47
43
|
)
|
data/spec/helpers/auth_spec.rb
CHANGED
|
@@ -8,21 +8,13 @@ describe Osso::Helpers::Auth do
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
subject(:app) do
|
|
11
|
-
Class.new {
|
|
12
|
-
include Osso::Helpers::Auth
|
|
13
|
-
}
|
|
11
|
+
Class.new { include Osso::Helpers::Auth }
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
describe 'with the token as a header' do
|
|
17
15
|
before do
|
|
18
16
|
allow_any_instance_of(subject).to receive(:request) do
|
|
19
|
-
double('Request', env: { '
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
allow_any_instance_of(subject).to receive(:session) do
|
|
23
|
-
{
|
|
24
|
-
admin_token: nil
|
|
25
|
-
}
|
|
17
|
+
double('Request', env: { 'admin_token' => token }, post?: false)
|
|
26
18
|
end
|
|
27
19
|
|
|
28
20
|
allow_any_instance_of(subject).to receive(:redirect) do
|
|
@@ -95,170 +87,6 @@ describe Osso::Helpers::Auth do
|
|
|
95
87
|
end
|
|
96
88
|
end
|
|
97
89
|
|
|
98
|
-
describe 'with the token as a parameter' do
|
|
99
|
-
before do
|
|
100
|
-
allow_any_instance_of(subject).to receive(:request) do
|
|
101
|
-
double('Request', env: {}, params: { 'admin_token' => token }, post?: false)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
allow_any_instance_of(subject).to receive(:session) do
|
|
105
|
-
{
|
|
106
|
-
admin_token: nil
|
|
107
|
-
}
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
allow_any_instance_of(subject).to receive(:redirect) do
|
|
111
|
-
false
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
describe 'with an admin token' do
|
|
116
|
-
let(:token) { encode({ scope: 'admin' }) }
|
|
117
|
-
|
|
118
|
-
it 'allows #token_protected! methods' do
|
|
119
|
-
expect(subject.new.token_protected!).to_not be(false)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
it 'allows #enterprise_protected! methods' do
|
|
123
|
-
expect(subject.new.enterprise_protected!).to_not be(false)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it 'allows #internal_protected! methods' do
|
|
127
|
-
expect(subject.new.internal_protected!).to_not be(false)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
it 'allows #admin_protected! methods' do
|
|
131
|
-
expect(subject.new.admin_protected!).to_not be(false)
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
describe 'with an internal token' do
|
|
136
|
-
let(:token) { encode({ scope: 'internal' }) }
|
|
137
|
-
|
|
138
|
-
it 'allows #token_protected! methods' do
|
|
139
|
-
expect(subject.new.token_protected!).to_not be(false)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
it 'allows #enterprise_protected! methods' do
|
|
143
|
-
expect(subject.new.enterprise_protected!).to_not be(false)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
it 'allows #internal_protected! methods' do
|
|
147
|
-
expect(subject.new.internal_protected!).to_not be(false)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
it 'allows #admin_protected! methods' do
|
|
151
|
-
expect(subject.new.admin_protected!).to be(false)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
describe 'with an end-user token' do
|
|
156
|
-
let(:token) { encode({ scope: 'end-user', email: 'user@example.com' }) }
|
|
157
|
-
|
|
158
|
-
it 'allows #token_protected! methods' do
|
|
159
|
-
expect(subject.new.token_protected!).to_not be(false)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
it 'allows #enterprise_protected! methods for the scoped domain' do
|
|
163
|
-
expect(subject.new.enterprise_protected!('example.com')).to_not be(false)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
it 'halts #enterprise_protected! methods for the wrong scoped domain' do
|
|
167
|
-
expect(subject.new.enterprise_protected!('foo.com')).to be(false)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
it 'halts #internal_protected! methods' do
|
|
171
|
-
expect(subject.new.internal_protected!).to be(false)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
it 'halts #admin_protected! methods' do
|
|
175
|
-
expect(subject.new.admin_protected!).to be(false)
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
describe 'with the token in session' do
|
|
181
|
-
before do
|
|
182
|
-
allow_any_instance_of(subject).to receive(:request) do
|
|
183
|
-
double('Request', env: {}, params: {}, post?: false)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
allow_any_instance_of(subject).to receive(:redirect) do
|
|
187
|
-
false
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
allow_any_instance_of(subject).to receive(:session).and_return(
|
|
191
|
-
{admin_token: token}.with_indifferent_access
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
describe 'with an admin token' do
|
|
197
|
-
let(:token) { encode({ scope: 'admin' }) }
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
it 'allows #token_protected! methods' do
|
|
201
|
-
expect(subject.new.token_protected!).to_not be(false)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
it 'allows #enterprise_protected! methods' do
|
|
205
|
-
expect(subject.new.enterprise_protected!).to_not be(false)
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
it 'allows #internal_protected! methods' do
|
|
209
|
-
expect(subject.new.internal_protected!).to_not be(false)
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
it 'allows #admin_protected! methods' do
|
|
213
|
-
expect(subject.new.admin_protected!).to_not be(false)
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
describe 'with an internal token' do
|
|
218
|
-
let(:token) { encode({ scope: 'internal' }) }
|
|
219
|
-
|
|
220
|
-
it 'allows #token_protected! methods' do
|
|
221
|
-
expect(subject.new.token_protected!).to_not be(false)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
it 'allows #enterprise_protected! methods' do
|
|
225
|
-
expect(subject.new.enterprise_protected!).to_not be(false)
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
it 'allows #internal_protected! methods' do
|
|
229
|
-
expect(subject.new.internal_protected!).to_not be(false)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
it 'allows #admin_protected! methods' do
|
|
233
|
-
expect(subject.new.admin_protected!).to be(false)
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
describe 'with an end-user token' do
|
|
238
|
-
let(:token) { encode({ scope: 'end-user', email: 'user@example.com' }) }
|
|
239
|
-
|
|
240
|
-
it 'allows #token_protected! methods' do
|
|
241
|
-
expect(subject.new.token_protected!).to_not be(false)
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
it 'allows #enterprise_protected! methods for the scoped domain' do
|
|
245
|
-
expect(subject.new.enterprise_protected!('example.com')).to_not be(false)
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
it 'halts #enterprise_protected! methods for the wrong scoped domain' do
|
|
249
|
-
expect(subject.new.enterprise_protected!('foo.com')).to be(false)
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
it 'halts #internal_protected! methods' do
|
|
253
|
-
expect(subject.new.internal_protected!).to be(false)
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
it 'halts #admin_protected! methods' do
|
|
257
|
-
expect(subject.new.admin_protected!).to be(false)
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
|
|
262
90
|
def encode(payload)
|
|
263
91
|
JWT.encode(
|
|
264
92
|
payload,
|
data/spec/routes/auth_spec.rb
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
5
|
describe Osso::Auth do
|
|
6
|
-
before do
|
|
7
|
-
described_class.set(:views, spec_views)
|
|
8
|
-
end
|
|
9
6
|
describe 'get /auth/saml/:uuid' do
|
|
10
7
|
describe 'for an Okta SAML provider' do
|
|
11
8
|
let(:enterprise) { create(:enterprise_with_okta) }
|
|
@@ -46,6 +43,7 @@ describe Osso::Auth do
|
|
|
46
43
|
nil,
|
|
47
44
|
{
|
|
48
45
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
46
|
+
'identity_provider' => okta_provider,
|
|
49
47
|
},
|
|
50
48
|
)
|
|
51
49
|
end.to change { Osso::Models::User.count }.by(1)
|
|
@@ -60,6 +58,7 @@ describe Osso::Auth do
|
|
|
60
58
|
nil,
|
|
61
59
|
{
|
|
62
60
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
61
|
+
'identity_provider' => okta_provider,
|
|
63
62
|
},
|
|
64
63
|
)
|
|
65
64
|
end.to change { Osso::Models::AuthorizationCode.count }.by(1)
|
|
@@ -74,6 +73,7 @@ describe Osso::Auth do
|
|
|
74
73
|
nil,
|
|
75
74
|
{
|
|
76
75
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
76
|
+
'identity_provider' => okta_provider,
|
|
77
77
|
},
|
|
78
78
|
)
|
|
79
79
|
expect(last_response).to be_redirect
|
|
@@ -99,6 +99,7 @@ describe Osso::Auth do
|
|
|
99
99
|
nil,
|
|
100
100
|
{
|
|
101
101
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
102
|
+
'identity_provider' => okta_provider,
|
|
102
103
|
},
|
|
103
104
|
)
|
|
104
105
|
end.to_not(change { Osso::Models::User.count })
|
|
@@ -109,9 +110,10 @@ describe Osso::Auth do
|
|
|
109
110
|
nil,
|
|
110
111
|
{
|
|
111
112
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
113
|
+
'identity_provider' => okta_provider,
|
|
112
114
|
},
|
|
113
115
|
)
|
|
114
|
-
expect(okta_provider.reload.status).to eq('
|
|
116
|
+
expect(okta_provider.reload.status).to eq('ACTIVE')
|
|
115
117
|
end
|
|
116
118
|
end
|
|
117
119
|
end
|
|
@@ -130,6 +132,7 @@ describe Osso::Auth do
|
|
|
130
132
|
nil,
|
|
131
133
|
{
|
|
132
134
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
135
|
+
'identity_provider' => azure_provider,
|
|
133
136
|
},
|
|
134
137
|
)
|
|
135
138
|
end.to change { Osso::Models::User.count }.by(1)
|
|
@@ -143,10 +146,11 @@ describe Osso::Auth do
|
|
|
143
146
|
nil,
|
|
144
147
|
{
|
|
145
148
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
149
|
+
'identity_provider' => azure_provider,
|
|
146
150
|
},
|
|
147
151
|
)
|
|
148
152
|
|
|
149
|
-
expect(azure_provider.reload.status).to eq('
|
|
153
|
+
expect(azure_provider.reload.status).to eq('ACTIVE')
|
|
150
154
|
end
|
|
151
155
|
end
|
|
152
156
|
|
|
@@ -166,6 +170,7 @@ describe Osso::Auth do
|
|
|
166
170
|
nil,
|
|
167
171
|
{
|
|
168
172
|
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
173
|
+
'identity_provider' => azure_provider,
|
|
169
174
|
},
|
|
170
175
|
)
|
|
171
176
|
end.to_not(change { Osso::Models::User.count })
|
|
@@ -173,40 +178,4 @@ describe Osso::Auth do
|
|
|
173
178
|
end
|
|
174
179
|
end
|
|
175
180
|
end
|
|
176
|
-
|
|
177
|
-
context 'with an invalid SAML response' do
|
|
178
|
-
describe 'post /auth/saml/:uuid/callback' do
|
|
179
|
-
let!(:enterprise) { create(:enterprise_with_azure) }
|
|
180
|
-
let!(:azure_provider) { enterprise.provider }
|
|
181
|
-
|
|
182
|
-
it 'raises an error when email is missing' do
|
|
183
|
-
mock_saml_omniauth(email: nil, id: SecureRandom.uuid)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
response = post(
|
|
187
|
-
"/auth/saml/#{azure_provider.id}/callback",
|
|
188
|
-
nil,
|
|
189
|
-
{
|
|
190
|
-
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
191
|
-
},
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
expect(response.body).to eq('Osso::Error::MissingSamlEmailAttributeError')
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
it 'raises an error when id is missing' do
|
|
198
|
-
mock_saml_omniauth(email: Faker::Internet.email, id: nil)
|
|
199
|
-
|
|
200
|
-
response = post(
|
|
201
|
-
"/auth/saml/#{azure_provider.id}/callback",
|
|
202
|
-
nil,
|
|
203
|
-
{
|
|
204
|
-
'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
|
|
205
|
-
},
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
expect(response.body).to eq('Osso::Error::MissingSamlIdAttributeError')
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
181
|
end
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<%= @error %>
|
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.gamma
|
|
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-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -273,11 +273,6 @@ 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/account_configuration_error.rb
|
|
277
|
-
- lib/osso/error/error.rb
|
|
278
|
-
- lib/osso/error/missing_saml_attribute_error.rb
|
|
279
|
-
- lib/osso/error/oauth_error.rb
|
|
280
|
-
- lib/osso/error/saml_config_error.rb
|
|
281
276
|
- lib/osso/graphql/.DS_Store
|
|
282
277
|
- lib/osso/graphql/mutation.rb
|
|
283
278
|
- lib/osso/graphql/mutations.rb
|
|
@@ -318,7 +313,6 @@ files:
|
|
|
318
313
|
- lib/osso/lib/app_config.rb
|
|
319
314
|
- lib/osso/lib/oauth2_token.rb
|
|
320
315
|
- lib/osso/lib/route_map.rb
|
|
321
|
-
- lib/osso/lib/saml_handler.rb
|
|
322
316
|
- lib/osso/models/access_token.rb
|
|
323
317
|
- lib/osso/models/app_config.rb
|
|
324
318
|
- lib/osso/models/authorization_code.rb
|
|
@@ -353,7 +347,6 @@ files:
|
|
|
353
347
|
- spec/graphql/query/identity_provider_spec.rb
|
|
354
348
|
- spec/graphql/query/oauth_clients_spec.rb
|
|
355
349
|
- spec/helpers/auth_spec.rb
|
|
356
|
-
- spec/lib/saml_handler_spec.rb
|
|
357
350
|
- spec/models/identity_provider_spec.rb
|
|
358
351
|
- spec/routes/admin_spec.rb
|
|
359
352
|
- spec/routes/app_spec.rb
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Osso
|
|
4
|
-
module Error
|
|
5
|
-
class AccountConfigurationError < Base; end
|
|
6
|
-
|
|
7
|
-
class MissingConfiguredIdentityProvider < AccountConfigurationError
|
|
8
|
-
def initialize(domain: 'The requested domain')
|
|
9
|
-
@domain = domain
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def message
|
|
13
|
-
"#{@domain} has no configured Identity Provider. " \
|
|
14
|
-
'SAML configuartion must be finalized before a user ' \
|
|
15
|
-
'for this domain can sign in with SSO.'
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
data/lib/osso/error/error.rb
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Osso
|
|
4
|
-
module Error
|
|
5
|
-
class Base < StandardError
|
|
6
|
-
def docs_url
|
|
7
|
-
nil
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
require_relative 'account_configuration_error'
|
|
14
|
-
require_relative 'missing_saml_attribute_error'
|
|
15
|
-
require_relative 'oauth_error'
|
|
16
|
-
require_relative 'saml_config_error'
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Osso
|
|
4
|
-
module Error
|
|
5
|
-
class MissingSamlAttributeError < Base; 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
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Osso
|
|
4
|
-
module Error
|
|
5
|
-
class OAuthError < Base
|
|
6
|
-
def docs_url
|
|
7
|
-
'https://ossoapp.com/docs/integration/oauth-consumption'
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
class NoAccountForOAuthClientError < OAuthError
|
|
12
|
-
def initialize(domain: 'the requested domain')
|
|
13
|
-
@domain = domain
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def message
|
|
17
|
-
"No customer account exists for #{@domain} and OAuth client pair. " \
|
|
18
|
-
"Review our OAuth documentation, and check you're using the correct OAuth client identifier. " \
|
|
19
|
-
'This usually suggests an engineering issue with your ENV variables.'
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
class InvalidOAuthClientIdentifier < OAuthError
|
|
24
|
-
def message
|
|
25
|
-
'No OAuth client exists for the requested OAuth client identifier. ' \
|
|
26
|
-
"Review our OAuth documentation, and ensure you're using a valid OAuth client identifier. " \
|
|
27
|
-
'OAuth credentials may have been regenerated by your team.'
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
class InvalidRedirectUri < OAuthError
|
|
32
|
-
def initialize(redirect_uri:)
|
|
33
|
-
@redirect_uri = redirect_uri
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def message
|
|
37
|
-
"The requested redirect URI #{@redirect_uri} is not on the allow-list for the rquested OAuth client identifier. " \
|
|
38
|
-
"Review our OAuth documentation, check you're using the correct OAuth client identifier, " \
|
|
39
|
-
'and confirm your Redirect URI allow-list includes the appropriate URI(s).'
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Osso
|
|
4
|
-
module Error
|
|
5
|
-
class SamlConfigError < Base
|
|
6
|
-
def message
|
|
7
|
-
'Something went wrong with your SAML configuration.'
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
class InvalidACSURLError < SamlConfigError
|
|
12
|
-
def message
|
|
13
|
-
'The ACS URL specfied in your Identity Provider configuration is malformed.'
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,85 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|