saml_camel 1.0.0 → 1.0.1
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/README.md +16 -4
- data/app/controllers/concerns/saml_camel/saml_service.rb +17 -12
- data/app/controllers/saml_camel/application_controller.rb +3 -0
- data/app/controllers/saml_camel/saml_controller.rb +47 -29
- data/app/models/saml_camel/application_record.rb +2 -1
- data/app/models/saml_camel/service_provider.rb +99 -71
- data/app/models/saml_camel/shib.rb +7 -12
- data/config/routes.rb +8 -2
- data/config/saml/development/idp_certificate.crt +25 -0
- data/config/saml/development/saml_certificate.crt +28 -0
- data/config/saml/development/saml_key.key +27 -0
- data/config/saml/development/settings.json +36 -0
- data/config/saml/production/idp_certificate.crt +25 -0
- data/config/saml/production/saml_certificate.crt +28 -0
- data/config/saml/production/saml_key.key +27 -0
- data/config/saml/production/settings.json +1 -0
- data/config/saml/test/idp_certificate.crt +25 -0
- data/config/saml/test/saml_certificate.crt +28 -0
- data/config/saml/test/saml_key.key +27 -0
- data/config/saml/test/settings.json +36 -0
- data/lib/saml_camel/engine.rb +3 -3
- data/lib/saml_camel/version.rb +3 -1
- data/lib/saml_camel.rb +81 -53
- data/lib/tasks/saml_camel_tasks.rake +73 -73
- metadata +33 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10d5430e20fe2c208e8f6b5c23eb6619cab0c2a1
|
4
|
+
data.tar.gz: c41a7aca05f8d811e970d49cca9b46dafdbbe3a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9ce2f9753fd6fe3dfc8ef4e3bd71b51d507ddc3b488f5c8a69312bc0ee739c71212e5d43e374bc603e1f0294a21a908bade01758e2609695a5550e64d4ebcd1
|
7
|
+
data.tar.gz: 710161acccafad9bd900433440881c96d6bca2f392cc0320a91e3225dfd85144d9a912ece2c084608f5d23d78b661870b5343469472073f9f6820537db6b3196
|
data/README.md
CHANGED
@@ -83,8 +83,10 @@ Identity Provider(idp) to recognize your app. Typically it should take the form
|
|
83
83
|
9. Logging is turned on by default. Logging is configured in `config/saml/development/settings.json`. To utilize logging saml_logging should be set to true (default), and primary_id must have a value. primary_id is the saml attribute you consider to be a primary identifier for a user
|
84
84
|
|
85
85
|
|
86
|
-
10.
|
87
|
-
|
86
|
+
10. Convenience Endpoints (assuming enginte is mounted to `saml` path):
|
87
|
+
- `/saml/attributes` view attributes being passed through
|
88
|
+
- `/saml/metadata` generate metadata for your sp
|
89
|
+
- `/saml/testAuthn` forces authentication and returns decrypted saml response, test auth path must be set to `true` in settings
|
88
90
|
|
89
91
|
## Example settings.json
|
90
92
|
```json
|
@@ -92,13 +94,16 @@ Identity Provider(idp) to recognize your app. Typically it should take the form
|
|
92
94
|
"_comment": "note you will need to restart the application when you make changes to this file",
|
93
95
|
"settings": {
|
94
96
|
"acs": "http://localhost:3000/saml/consumeSaml",
|
95
|
-
"
|
97
|
+
"raw_response_acs": "http://localhost:3000/saml/consumeSaml/rawResponse",
|
98
|
+
"entity_id": "https://samlCamel.com/doesNotHaveToResolve",
|
96
99
|
"sso_url": "https://shib.oit.duke.edu/idp/profile/SAML2/Redirect/SSO",
|
97
100
|
"logout_url": "https://shib.oit.duke.edu/cgi-bin/logout.pl",
|
98
101
|
"primary_id": "eduPersonPrincipalName",
|
99
102
|
"sp_session_timeout": 1,
|
100
103
|
"sp_session_lifetime": 8,
|
101
|
-
"
|
104
|
+
"test_auth_path": true,
|
105
|
+
"saml_logging": true,
|
106
|
+
"debug": false
|
102
107
|
},
|
103
108
|
"attribute_map": {
|
104
109
|
"urn:oid:1.3.6.1.4.1.5923.1.1.1.9": "eduPersonScopedAffiliation",
|
@@ -123,6 +128,13 @@ Identity Provider(idp) to recognize your app. Typically it should take the form
|
|
123
128
|
}
|
124
129
|
```
|
125
130
|
|
131
|
+
## Testing
|
132
|
+
If saml_protect is called during testing the app will more than likely hang as it is
|
133
|
+
waiting for a user to authenticate at the idp. To get around this you have two options:
|
134
|
+
|
135
|
+
1. You can not call saml_protect in the test environment and mock your users
|
136
|
+
2. Use `SamlCaml::ServiceProvider.mock_saml_cache(permit_key: 'some permit key', ip_address: 'enter your host ip here')`. This will setup a cache(note caching for test must be enabled). In addition you will need to mock to session variables. set `session[:sp_session]` to `Time.now` and set mock attributes in `session[:saml_attributes]`
|
137
|
+
|
126
138
|
|
127
139
|
## License
|
128
140
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -1,6 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_dependency 'saml_camel/application_controller'
|
4
|
+
|
5
|
+
# Helper Methods for SamlController
|
6
|
+
module SamlCamel::SamlService # rubocop:disable Style/ClassAndModuleChildren
|
4
7
|
extend ActiveSupport::Concern
|
5
8
|
|
6
9
|
def cache_available?(app_cache)
|
@@ -12,24 +15,26 @@ module SamlCamel::SamlService
|
|
12
15
|
end
|
13
16
|
end
|
14
17
|
|
15
|
-
|
18
|
+
# TODO: refactor
|
19
|
+
def saml_protect # rubocop:disable Metrics/MethodLength, Metrics/AbcSize:
|
16
20
|
user_cache = cache_available?(Rails.cache.fetch(session[:saml_session_id])) if session[:saml_session_id]
|
17
21
|
if session[:saml_session_id] && user_cache
|
18
|
-
sp = SamlCamel::ServiceProvider.new(
|
19
|
-
|
20
|
-
|
22
|
+
sp = SamlCamel::ServiceProvider.new(
|
23
|
+
cache_permit_key: session[:saml_session_id].to_sym,
|
24
|
+
saml_attributes: session[:saml_attributes]
|
25
|
+
)
|
26
|
+
session[:sp_session] = sp.validate_sp_session(session[:sp_session], request.remote_ip)
|
27
|
+
unless session[:saml_response_success] || session[:sp_session]
|
21
28
|
saml_request_url = sp.generate_saml_request(request)
|
22
29
|
redirect_to(saml_request_url)
|
23
30
|
end
|
24
|
-
|
25
31
|
else
|
26
32
|
session[:saml_session_id] = SamlCamel::ServiceProvider.generate_permit_key
|
27
|
-
saml_request_url = SamlCamel::ServiceProvider.new(
|
33
|
+
saml_request_url = SamlCamel::ServiceProvider.new(
|
34
|
+
cache_permit_key: session[:saml_session_id].to_sym
|
35
|
+
).generate_saml_request(request)
|
28
36
|
redirect_to(saml_request_url)
|
29
37
|
end
|
30
|
-
session[:saml_response_success] = nil #keeps us from looping
|
38
|
+
session[:saml_response_success] = nil # keeps us from looping
|
31
39
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
40
|
end
|
@@ -1,53 +1,54 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_dependency 'saml_camel/application_controller'
|
3
4
|
module SamlCamel
|
5
|
+
# handles SamlCamel SP
|
4
6
|
class SamlController < ApplicationController
|
5
7
|
include SamlCamel::SamlService
|
6
|
-
skip_before_action :verify_authenticity_token
|
8
|
+
skip_before_action :verify_authenticity_token, only: %i[consume logout raw_response]
|
7
9
|
before_action :saml_protect, only: [:attr_check]
|
8
10
|
|
11
|
+
def attr_check; end
|
12
|
+
# consumes the saml response from the IDP
|
13
|
+
# TODO break out into separate methods if possible
|
14
|
+
# TODO rubocop also suggests to many assignments going on in consume
|
9
15
|
|
10
|
-
|
11
|
-
#convinence route to see attributes that are coming through
|
12
|
-
def index
|
13
|
-
@attributes = session[:saml_attributes]
|
14
|
-
end
|
15
|
-
|
16
|
-
|
17
|
-
#consumes the saml response from the IDP
|
18
|
-
def consume
|
16
|
+
def consume # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
19
17
|
permit_key = session[:saml_session_id].to_sym
|
20
|
-
user_cache =
|
21
|
-
|
18
|
+
user_cache = Rails.cache.fetch(permit_key)
|
19
|
+
unless cache_available?(user_cache)
|
20
|
+
raise 'Unable to access cache. Ensure cache is configured according to documentation.'
|
21
|
+
end
|
22
22
|
redirect_path = user_cache[:redirect_url]
|
23
|
-
sp = SamlCamel::ServiceProvider.new(
|
23
|
+
sp = SamlCamel::ServiceProvider.new(
|
24
|
+
cache_permit_key: permit_key, saml_attributes: session[:saml_attributes]
|
25
|
+
)
|
24
26
|
ol_response = SamlCamel::ServiceProvider.ol_response(params[:SAMLResponse])
|
25
27
|
|
26
|
-
if sp.validate_idp_response(ol_response,request.remote_ip)
|
28
|
+
if sp.validate_idp_response(ol_response, request.remote_ip)
|
27
29
|
# authorize_success, log the user
|
28
30
|
session[:saml_response_success] = true
|
29
31
|
sp.set_saml_session_lifetime
|
30
32
|
session[:sp_session] = Time.now
|
31
|
-
|
32
33
|
session[:saml_attributes] = SamlCamel::Transaction.map_attributes(ol_response.attributes)
|
33
34
|
SamlCamel::Logging.successful_auth(session[:saml_attributes])
|
34
35
|
|
35
36
|
redirect_to redirect_path
|
36
37
|
else # otherwise list out the errors in the response
|
37
|
-
if
|
38
|
+
if session[:saml_session_id]
|
38
39
|
permit_key = session[:saml_session_id].to_sym
|
39
40
|
Rails.cache.delete(permit_key)
|
40
41
|
session[:saml_session_id] = nil
|
41
42
|
end
|
42
43
|
session[:sp_session] = nil
|
43
44
|
session[:saml_response_success] = false
|
44
|
-
response.errors
|
45
|
+
# response.errors
|
45
46
|
SamlCamel::Logging.auth_failure(ol_response.errors)
|
46
47
|
|
47
|
-
redirect_to action:
|
48
|
+
redirect_to action: 'failure', locals: { errors: ol_response.errors }
|
48
49
|
end
|
49
|
-
rescue => e
|
50
|
-
if
|
50
|
+
rescue StandardError => e
|
51
|
+
if session[:saml_session_id]
|
51
52
|
permit_key = session[:saml_session_id].to_sym
|
52
53
|
Rails.cache.delete(permit_key)
|
53
54
|
end
|
@@ -56,35 +57,52 @@ module SamlCamel
|
|
56
57
|
session[:sp_session] = nil
|
57
58
|
|
58
59
|
SamlCamel::Logging.auth_failure(e)
|
59
|
-
redirect_to action:
|
60
|
+
redirect_to action: 'failure', locals: { errors: e }
|
60
61
|
end
|
61
62
|
|
62
|
-
|
63
|
-
#route to show saml failures
|
63
|
+
# route to show saml failures
|
64
64
|
def failure
|
65
65
|
@error = params[:locals][:errors]
|
66
66
|
end
|
67
67
|
|
68
|
+
# convinence route to see attributes that are coming through
|
69
|
+
def index
|
70
|
+
@attributes = session[:saml_attributes]
|
71
|
+
end
|
68
72
|
|
69
|
-
#kills SP session and redirects to IDP to kill idp session
|
73
|
+
# kills SP session and redirects to IDP to kill idp session
|
70
74
|
def logout
|
71
75
|
SamlCamel::Logging.logout(session[:saml_attributes])
|
72
76
|
session[:saml_attributes] = nil
|
73
77
|
session[:sp_session] = nil
|
74
78
|
|
75
|
-
# return_url = SamlCamel::Transaction.logout #this methods logs the user
|
76
|
-
|
79
|
+
# return_url = SamlCamel::Transaction.logout #this methods logs the user
|
80
|
+
# out of the IDP, and returns a url to be redirected to
|
81
|
+
redirect_to SP_SETTINGS['settings']['logout_url']
|
77
82
|
end
|
78
83
|
|
84
|
+
# generate a metadata page that you may need to share with an IDP
|
85
|
+
def metadata
|
86
|
+
settings = SamlCamel::Transaction.saml_settings
|
87
|
+
meta = OneLogin::RubySaml::Metadata.new
|
88
|
+
render xml: meta.generate(settings)
|
89
|
+
end
|
79
90
|
|
80
|
-
|
91
|
+
# meant to force authn and then be redirected to raw response to view raw decrypted response
|
92
|
+
def force_authn
|
93
|
+
saml_request_url = SamlCamel::ServiceProvider.new.generate_saml_request(request, force_authn: true)
|
94
|
+
redirect_to(saml_request_url)
|
81
95
|
end
|
82
96
|
|
97
|
+
def raw_response
|
98
|
+
ol_response = SamlCamel::ServiceProvider.ol_response(params[:SAMLResponse], raw_response: true)
|
99
|
+
render xml: ol_response.decrypted_document.to_s if ol_response.is_valid?
|
100
|
+
end
|
83
101
|
|
84
102
|
private
|
103
|
+
|
85
104
|
def saml_settings
|
86
105
|
SamlCamel::Transaction.saml_settings
|
87
106
|
end
|
88
|
-
|
89
107
|
end
|
90
108
|
end
|
@@ -1,121 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SamlCamel
|
4
|
+
# class serves as core buisness logic for SamlCamle SP
|
2
5
|
class ServiceProvider
|
3
|
-
attr_reader :cache_permit_key, :
|
6
|
+
attr_reader :cache_permit_key, :saml_attributes
|
4
7
|
|
5
|
-
def initialize(cache_permit_key: nil,
|
6
|
-
@cache_permit_key = cache_permit_key.to_sym
|
8
|
+
def initialize(cache_permit_key: nil, saml_attributes: nil)
|
9
|
+
@cache_permit_key = cache_permit_key.try(:to_sym)
|
7
10
|
@saml_attributes = saml_attributes
|
8
11
|
@user_cache = Rails.cache.fetch(@cache_permit_key)
|
9
12
|
end
|
10
13
|
|
11
|
-
|
12
14
|
def self.generate_permit_key
|
13
|
-
secure_random = SecureRandom.base64.chomp.gsub(
|
15
|
+
secure_random = SecureRandom.base64.chomp.gsub(/\W/, '')
|
14
16
|
"samlCamel#{secure_random}"
|
15
17
|
end
|
16
18
|
|
17
|
-
def self.
|
18
|
-
|
19
|
-
|
19
|
+
def self.mock_saml_cache(permit_key: nil, ip_address: nil)
|
20
|
+
lifetime = SP_SETTINGS['settings']['sp_session_lifetime']
|
21
|
+
Rails.cache.fetch(@cache_permit_key, expires_in: lifetime.hours) do
|
22
|
+
{ ip_address: host_request.remote_ip }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# ol OneLogin
|
27
|
+
def self.ol_response(idp_response, raw_response: false)
|
28
|
+
settings = SamlCamel::Transaction.saml_settings(raw_response: raw_response)
|
29
|
+
response = OneLogin::RubySaml::Response.new(idp_response, settings: settings)
|
30
|
+
response.settings = settings
|
20
31
|
|
21
32
|
response
|
22
33
|
end
|
23
34
|
|
24
|
-
|
25
|
-
def check_expired_session(sp_session)
|
26
|
-
|
27
|
-
|
28
|
-
sp_lifetime = SP_SETTINGS['settings']["sp_session_lifetime"]
|
35
|
+
# TODO: method too complex
|
36
|
+
def check_expired_session(sp_session) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/LineLength
|
37
|
+
sp_timeout = SP_SETTINGS['settings']['sp_session_timeout']
|
38
|
+
sp_lifetime = SP_SETTINGS['settings']['sp_session_lifetime']
|
29
39
|
|
30
40
|
set_saml_session_lifetime if @user_cache[:session_start_time].nil?
|
31
41
|
sp_session_init_time = @user_cache[:session_start_time]
|
32
42
|
|
33
|
-
|
43
|
+
SamlCamel::Logging.debug('Checking if session expired') if SP_DEBUG
|
34
44
|
######## set session[:sp_session] maybe a seperate method
|
35
45
|
if sp_session
|
36
|
-
|
37
|
-
|
38
|
-
if (Time.now - Time.parse(sp_session)) < sp_timeout.hour
|
39
|
-
return Time.now
|
40
|
-
else
|
46
|
+
# if the session has exceeded the allowed lifetime, remove session
|
47
|
+
if (Time.now - sp_session_init_time) > sp_lifetime.hour
|
41
48
|
SamlCamel::Logging.expired_session(@saml_attributes)
|
42
49
|
return nil
|
43
50
|
end
|
44
51
|
|
45
|
-
#if the session has
|
46
|
-
if (Time.now -
|
52
|
+
# if the session has timed out remove session, otherwise refresh
|
53
|
+
if (Time.now - Time.parse(sp_session)) < sp_timeout.hour
|
54
|
+
SamlCamel::Logging.debug('Session within timeout, session renewed') if SP_DEBUG
|
55
|
+
Time.now
|
56
|
+
else
|
47
57
|
SamlCamel::Logging.expired_session(@saml_attributes)
|
48
|
-
return
|
58
|
+
return nil
|
49
59
|
end
|
50
|
-
else #if no sp session return nil
|
60
|
+
else # if no sp session return nil
|
61
|
+
SamlCamel::Logging.debug('No session found when checking expiration') if SP_DEBUG
|
51
62
|
return nil
|
52
63
|
end
|
53
64
|
end
|
54
65
|
|
66
|
+
# TODO: method too complex
|
67
|
+
def duplicate_response_id?(response_id, count: 3) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/LineLength
|
68
|
+
SamlCamel::Logging.debug('Checking uniqueness of response id') if SP_DEBUG
|
55
69
|
|
56
|
-
|
57
|
-
#use semaphore to only allow 1 thread at a time to access
|
70
|
+
# use semaphore to only allow 1 thread at a time to access
|
58
71
|
@semaphore ||= Mutex.new
|
59
|
-
@semaphore.synchronize {
|
60
|
-
ids = Rails.cache.fetch(
|
72
|
+
@semaphore.synchronize { # rubocop:disable Style/BlockDelimiters
|
73
|
+
ids = Rails.cache.fetch('saml_camel_response_ids')
|
61
74
|
if ids
|
62
75
|
if ids.include?(response_id)
|
63
|
-
|
64
|
-
raise
|
76
|
+
SamlCamel::Logging.debug('Response id has already been used') if SP_DEBUG
|
77
|
+
raise 'SAML response ID already issued.'
|
65
78
|
else
|
79
|
+
SamlCamel::Logging.debug("Unique Response ID #{response_id}") if SP_DEBUG
|
66
80
|
ids << response_id
|
67
|
-
Rails.cache.fetch(
|
81
|
+
Rails.cache.fetch('saml_camel_response_ids', expires_in: 1.hours) do
|
68
82
|
ids
|
69
83
|
end
|
70
84
|
end
|
71
85
|
else
|
72
|
-
|
86
|
+
SamlCamel::Logging.debug("Unique Response ID #{response_id}") if SP_DEBUG
|
87
|
+
Rails.cache.fetch('saml_camel_response_ids', expires_in: 1.hours) do
|
73
88
|
[response_id]
|
74
89
|
end
|
75
90
|
end
|
76
91
|
}
|
77
92
|
rescue ThreadError
|
78
|
-
|
79
|
-
|
93
|
+
puts 'locked ' * 50
|
94
|
+
# SamlCamel::Logging.debug('Response ID check locked, trying again') if SP_DEBUG
|
95
|
+
if count.positive? # rubocop:disable Style/GuardClause
|
80
96
|
sleep(0.1)
|
81
97
|
duplicate_response_id?(response_id, count: count - 1)
|
82
|
-
else raise
|
98
|
+
else raise 'Resposne ID Validation Error'
|
83
99
|
end
|
84
100
|
end
|
85
101
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
def generate_saml_request(host_request)
|
102
|
+
# generates a saml requests and establishes a cache for the user
|
103
|
+
def generate_saml_request(host_request, force_authn: false)
|
104
|
+
SamlCamel::Logging.debug("Creating request for #{host_request.remote_ip}") if SP_DEBUG
|
90
105
|
request = OneLogin::RubySaml::Authrequest.new
|
91
|
-
lifetime =
|
106
|
+
lifetime = SP_SETTINGS['settings']['sp_session_lifetime']
|
92
107
|
|
93
|
-
#store ip address and original url request in memory to be used for
|
108
|
+
# store ip address and original url request in memory to be used for
|
109
|
+
# verification and redirect after response
|
94
110
|
Rails.cache.fetch(@cache_permit_key, expires_in: lifetime.hours) do
|
95
|
-
{ip_address: host_request.remote_ip,
|
111
|
+
{ ip_address: host_request.remote_ip, redirect_url: host_request.url }
|
96
112
|
end
|
97
|
-
request.create(SamlCamel::Transaction.saml_settings)
|
113
|
+
request.create(SamlCamel::Transaction.saml_settings(raw_response: force_authn))
|
98
114
|
end
|
99
115
|
|
100
|
-
|
116
|
+
|
117
|
+
# set saml_session lifetime, called if none set
|
118
|
+
# TODO: this may need to be renamed, it's not really setting the lifetime
|
119
|
+
# it's refreshing the last time a user authenticated
|
101
120
|
def set_saml_session_lifetime
|
102
121
|
user_saml_cache = Rails.cache.fetch(@cache_permit_key)
|
103
122
|
user_saml_cache[:session_start_time] = Time.now
|
104
|
-
sp_lifetime = SP_SETTINGS['settings'][
|
123
|
+
sp_lifetime = SP_SETTINGS['settings']['sp_session_lifetime']
|
124
|
+
|
125
|
+
SamlCamel::Logging.debug("Setting lifetime of session. Lifetime of #{sp_lifetime} hours") if SP_DEBUG
|
105
126
|
Rails.cache.fetch(@cache_permit_key, expires_in: sp_lifetime.hours) do
|
106
|
-
user_saml_cache
|
127
|
+
user_saml_cache
|
107
128
|
end
|
108
129
|
end
|
109
130
|
|
110
|
-
#NOTE these methods will raise errors if not a valid response_id
|
111
|
-
|
112
|
-
def validate_idp_response(response,remote_ip)
|
131
|
+
# NOTE these methods will raise errors if not a valid response_id
|
132
|
+
# which in turn will trigger a resuce that kills the sp session
|
133
|
+
def validate_idp_response(response, remote_ip)
|
134
|
+
SamlCamel::Logging.debug('Validating IDP response') if SP_DEBUG
|
113
135
|
if response.is_valid?
|
114
|
-
#validate not sha1
|
136
|
+
# validate not sha1
|
115
137
|
verify_sha_type(response)
|
116
138
|
|
117
|
-
#validate IP address
|
118
|
-
raise
|
139
|
+
# validate IP address
|
140
|
+
raise 'IP mismatch error' unless validate_ip(remote_ip)
|
119
141
|
|
120
142
|
response_id = response.id(response.document)
|
121
143
|
duplicate_response_id?(response_id)
|
@@ -126,36 +148,42 @@ module SamlCamel
|
|
126
148
|
end
|
127
149
|
end
|
128
150
|
|
129
|
-
#validate that ip address has not changed
|
151
|
+
# validate that ip address has not changed
|
130
152
|
def validate_ip(remote_ip)
|
131
|
-
|
153
|
+
if SP_DEBUG
|
154
|
+
SamlCamel::Logging.debug(
|
155
|
+
"Validating IP consistancy. IP @ request = #{@user_cache[:ip_address]}
|
156
|
+
| current IP = #{remote_ip}"
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
132
160
|
return true if remote_ip == @user_cache[:ip_address]
|
133
|
-
SamlCamel::Logging.bad_ip(@saml_attributes,
|
134
|
-
|
161
|
+
SamlCamel::Logging.bad_ip(@saml_attributes,
|
162
|
+
@user_cache[:ip_address],
|
163
|
+
remote_ip)
|
164
|
+
nil
|
135
165
|
end
|
136
166
|
|
137
|
-
#validates a the sp timestamps and ip. returns a new timestamp if
|
138
|
-
# otherwise returns nil
|
139
|
-
|
167
|
+
# validates a the sp timestamps and ip. returns a new timestamp if
|
168
|
+
# everything is good otherwise returns nil
|
169
|
+
|
170
|
+
def validate_sp_session(sp_session, remote_ip)
|
140
171
|
new_session = check_expired_session(sp_session)
|
141
|
-
if new_session
|
142
|
-
|
143
|
-
else
|
144
|
-
return nil
|
145
|
-
end
|
146
|
-
return new_session if has_valid_ip
|
172
|
+
valid_ip = validate_ip(remote_ip) if new_session
|
173
|
+
return new_session if new_session && valid_ip
|
147
174
|
end
|
148
175
|
|
149
|
-
|
150
176
|
def verify_sha_type(response)
|
151
|
-
|
152
|
-
|
177
|
+
SamlCamel::Logging.debug('Verify response is not SHA1') if SP_DEBUG
|
178
|
+
|
179
|
+
# was suggested to use an xml parser, however was having great difficulyt
|
180
|
+
# with nokogiri open to trying a different parser or advice on nokogiri as
|
181
|
+
# it's not reacting as I would typically expect it to
|
153
182
|
raw_xml_string = response.decrypted_document.to_s
|
154
|
-
attr_scan = raw_xml_string.scan(
|
155
|
-
is_sha1 = attr_scan[0].match(
|
183
|
+
attr_scan = raw_xml_string.scan(%r{<ds:SignatureMethod.*\/>})
|
184
|
+
is_sha1 = attr_scan[0].match('sha1')
|
156
185
|
|
157
|
-
raise
|
186
|
+
raise 'SHA1 algorithm not supported' if is_sha1
|
158
187
|
end
|
159
|
-
|
160
188
|
end
|
161
189
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SamlCamel
|
4
|
+
# handle shib attributes
|
2
5
|
class Shib
|
3
|
-
|
4
|
-
ATTRIBUTE_MAP = JSON.parse(File.read("config/saml/shibboleth.json"))
|
6
|
+
ATTRIBUTE_MAP = JSON.parse(File.read('config/saml/shibboleth.json'))
|
5
7
|
|
6
8
|
def self.attributes(request)
|
7
9
|
attrs = {}
|
@@ -11,16 +13,9 @@ module SamlCamel
|
|
11
13
|
attrs
|
12
14
|
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
identity
|
16
|
+
# sets shib headers directly
|
17
|
+
def self.set_headers(request: nil, identity: nil)
|
18
|
+
identity.each { |k, v| request.env[k] = v }
|
18
19
|
end
|
19
|
-
|
20
|
-
#sets shib headers directly
|
21
|
-
def self.set_headers(request: nil,identity: nil)
|
22
|
-
identity.each {|k,v| request.env[k] = v }
|
23
|
-
end
|
24
|
-
|
25
20
|
end
|
26
21
|
end
|
data/config/routes.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
+
settings = JSON.parse(File.read("config/saml/#{Rails.env}/settings.json"))
|
2
|
+
# NOTE there are two saml dirs, 1 in config and 1 in dummy. They need to be in
|
3
|
+
# both places so users can run their tests, but the gem can develop with it's
|
4
|
+
# own tests as well
|
5
|
+
|
1
6
|
SamlCamel::Engine.routes.draw do
|
2
7
|
get "/" => "saml#index"
|
3
8
|
get "/attributes" => 'saml#attr_check'
|
4
9
|
get "/failure" => 'saml#failure'
|
10
|
+
get "/metadata" => 'saml#metadata'
|
11
|
+
get "/testAuthn" => 'saml#force_authn' if settings.dig('settings', 'test_auth_path')
|
5
12
|
post "/consumeSaml" => "saml#consume"
|
13
|
+
post "/consumeSaml/rawResponse" => "saml#raw_response"
|
6
14
|
post "/logout" => "saml#logout"
|
7
15
|
end
|
8
|
-
#TODO check ip_address stored by the IDP
|
9
|
-
#TODO chceck ip address on every check ra
|
@@ -0,0 +1,25 @@
|
|
1
|
+
MIIEWjCCA0KgAwIBAgIJAP1rB/FjRgy6MA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV
|
2
|
+
BAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEPMA0GA1UEBxMGRHVyaGFt
|
3
|
+
MRgwFgYDVQQKEw9EdWtlIFVuaXZlcnNpdHkxDDAKBgNVBAsTA09JVDEaMBgGA1UE
|
4
|
+
AxMRc2hpYi5vaXQuZHVrZS5lZHUwHhcNMTAwOTA5MTI0NDU1WhcNMjgwOTA0MTI0
|
5
|
+
NDU1WjB7MQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzAN
|
6
|
+
BgNVBAcTBkR1cmhhbTEYMBYGA1UEChMPRHVrZSBVbml2ZXJzaXR5MQwwCgYDVQQL
|
7
|
+
EwNPSVQxGjAYBgNVBAMTEXNoaWIub2l0LmR1a2UuZWR1MIIBIjANBgkqhkiG9w0B
|
8
|
+
AQEFAAOCAQ8AMIIBCgKCAQEAt+hnl6gSRi0Y8VuNl6PCPYejj7VfVs/y8bRa5zAY
|
9
|
+
RHwb75+vBSs2j1yeUcSore9Ba5Ni7v947V34afRMGRPOqr4TEDZxU+1Bg0zAvSrR
|
10
|
+
n4Y8B+zyJuhtOpmOZzTwE9o/Oc+CB4kYV/K0woKZdcoxHJm8TbqBqdxU4fFYUlNU
|
11
|
+
o4Dr5jRdCSr9MHBOqGWXtQMg16qYNB7StNk4twY29FNnpZwkVTfsE76uVsRMkG8i
|
12
|
+
6/RiHpXZ/ioOOqndptbEGdsOIE3ivAJOZdvYwnDe5NnTH06P01HsxH3OOnYqhuG2
|
13
|
+
J6qdhqoelGeHRG+jfl8YkYXCcKQvja2tJ5G+6iqSN7DP6QIDAQABo4HgMIHdMB0G
|
14
|
+
A1UdDgQWBBQHYXwB6otkfyMOmUI59j8823hFRDCBrQYDVR0jBIGlMIGigBQHYXwB
|
15
|
+
6otkfyMOmUI59j8823hFRKF/pH0wezELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5v
|
16
|
+
cnRoIENhcm9saW5hMQ8wDQYDVQQHEwZEdXJoYW0xGDAWBgNVBAoTD0R1a2UgVW5p
|
17
|
+
dmVyc2l0eTEMMAoGA1UECxMDT0lUMRowGAYDVQQDExFzaGliLm9pdC5kdWtlLmVk
|
18
|
+
dYIJAP1rB/FjRgy6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAG7q
|
19
|
+
wJpiSLJbx2gj/cGDYeuBW/CeRGNghjQ/mb076P3WXsRNPAimcXulSUbQkS6eDH4t
|
20
|
+
Ifvsa0jf4FRsEOwH/x8354/0wyv4RwuavX25kjpmoFn3O+eKokyzsc7/Q2gsm0mv
|
21
|
+
V8XQo+5b+4we8AFYlAVp26nLeIqAiJM8xZJ9yHuzVL1O4yxIWIKECWHLqY5+1nas
|
22
|
+
XNiLURrHhsK5pZUPLuhzJFgZuJT62TtnrjJXlrRhJ389VSkh6R64C6ncjNkg6/Cu
|
23
|
+
tA6SX0infqNRyPRNJK+bnQd1yOP4++tjD/lAPE+5tiD/waI3fArt43ZE/qp7pYMS
|
24
|
+
9TEfyQ5QpfRYAUFWXBc=
|
25
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIEuTCCA6GgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBnTELMAkGA1UEBhMCVVMx
|
3
|
+
FzAVBgNVBAgMDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQHDAZEdXJoYW0xGDAWBgNV
|
4
|
+
BAoMD0R1a2UgVW5pdmVyc2l0eTEMMAoGA1UECwwDT0lUMR0wGwYDVQQDDBRzYW1s
|
5
|
+
IGNhbWVsIGR1bW15IGFwcDEdMBsGCSqGSIb3DQEJARYOZGExMjlAZHVrZS5lZHUw
|
6
|
+
HhcNMTgwNTIyMTcxMDMwWhcNMTkwNTIyMTcxMDMwWjCBnTELMAkGA1UEBhMCVVMx
|
7
|
+
FzAVBgNVBAgMDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQHDAZEdXJoYW0xGDAWBgNV
|
8
|
+
BAoMD0R1a2UgVW5pdmVyc2l0eTEMMAoGA1UECwwDT0lUMR0wGwYDVQQDDBRzYW1s
|
9
|
+
IGNhbWVsIGR1bW15IGFwcDEdMBsGCSqGSIb3DQEJARYOZGExMjlAZHVrZS5lZHUw
|
10
|
+
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+OHs74gT5AmdSsLgHETvX
|
11
|
+
50+S0NgWp5dcovfuMYFV+1CFX1MhgjhBQSwkA9U/0pfKf/eoU18O2gI2y46OK8j2
|
12
|
+
e5oyUuKv1UQWe2RHKvxvNrwvvUVcLY4mJDZf0d4q6EyTVo2aWHwoskxnQpjbusgp
|
13
|
+
Vq178Jfaeu/QaiBtq82vPlu0tfCeOXIyEdyRiOyc2bQvS5MW6FvzWtgatiNUnJJe
|
14
|
+
sBM/JUiFOvf3qG7LHEzpaIBmoHBwxG5b3yjrGgGTdw+5gyXdPEwEeiTddMvYlXWM
|
15
|
+
t+VMoTmsaBxrXRJBvpLxGWHZRb0VcoVTqWjcKVD/hR0A7H6ogaoOatHDWM41b3ZL
|
16
|
+
AgMBAAGjggEAMIH9MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFGh/Y36w7wcL
|
17
|
+
nLXFC0dUpboAAV+ZMIHKBgNVHSMEgcIwgb+AFGh/Y36w7wcLnLXFC0dUpboAAV+Z
|
18
|
+
oYGjpIGgMIGdMQswCQYDVQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmEx
|
19
|
+
DzANBgNVBAcMBkR1cmhhbTEYMBYGA1UECgwPRHVrZSBVbml2ZXJzaXR5MQwwCgYD
|
20
|
+
VQQLDANPSVQxHTAbBgNVBAMMFHNhbWwgY2FtZWwgZHVtbXkgYXBwMR0wGwYJKoZI
|
21
|
+
hvcNAQkBFg5kYTEyOUBkdWtlLmVkdYIBADANBgkqhkiG9w0BAQsFAAOCAQEAFE/X
|
22
|
+
DPipapLFDnu2jCMR4lhDeEF2Pm1DIibiy6ZvmzCstj++MYOI7gKkUgeUUhFTEQIV
|
23
|
+
fZIo5gIWkyoPVOwGALLTme01Tdk3Mul4pV0iqMn4k3F9NsC9wRy4WR2yPF9GYa/e
|
24
|
+
ktK+ZBYt/2SZA4vS5q63jsMC0TjkrTGJokXohwScWDc4kIFfvU6biWW7zBCVfpaa
|
25
|
+
YfsLYNBTbZ7VqEVFzcpYv8LBTOYoToAS5+yuAwrIdPEfqx3R4tIwGCik4tSByQFO
|
26
|
+
i/VvEL5rTWhmUrKPh1hriPVYZ9gW2Mk87Snlyswsqv5d8+ITVgF+RL+cutUA29C+
|
27
|
+
moSLPLaWINlhqvuRXw==
|
28
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEogIBAAKCAQEAvjh7O+IE+QJnUrC4BxE71+dPktDYFqeXXKL37jGBVftQhV9T
|
3
|
+
IYI4QUEsJAPVP9KXyn/3qFNfDtoCNsuOjivI9nuaMlLir9VEFntkRyr8bza8L71F
|
4
|
+
XC2OJiQ2X9HeKuhMk1aNmlh8KLJMZ0KY27rIKVate/CX2nrv0GogbavNrz5btLXw
|
5
|
+
njlyMhHckYjsnNm0L0uTFuhb81rYGrYjVJySXrATPyVIhTr396huyxxM6WiAZqBw
|
6
|
+
cMRuW98o6xoBk3cPuYMl3TxMBHok3XTL2JV1jLflTKE5rGgca10SQb6S8Rlh2UW9
|
7
|
+
FXKFU6lo3ClQ/4UdAOx+qIGqDmrRw1jONW92SwIDAQABAoIBAHZpuKU9fPT5/xHl
|
8
|
+
upmDq+oqL0nowivQJhRfytE3dhjtOmHcRma8poJQrMa6sBxr31wKr0PUqn8XTXuI
|
9
|
+
2fQ843w003dyS3VD4H/STklTRBODUkCxpSTNowixUDvz7EZvl4O8xKeJX7kBzTgW
|
10
|
+
qAtYydOaBqL50b4K+5CVEBzVb1Qf/DKhCbBeYvnwAcUVT+t5lDGUh+54pLTHmeGZ
|
11
|
+
2as+1MeBWLMR/ynMDziVVR3XIM02+pHPEwiI9ZTazUAKRJnskb5gBpHqtGiZSijC
|
12
|
+
zQq+GSnnBPvvc0gtjqf+KF/6NLy/zDGmpF1e+blCnnLPUQGPTkClq59EHdn8jedO
|
13
|
+
YyRrWmkCgYEA9VqRMziTAi79yP2rLqE7cMKPDtrOilHK8fDk5N2xxzEsVoKUsotq
|
14
|
+
x384sfmrA3oVSNQsPi/DF16eH1cLaQL86rTaUKl4DqO6rLBPhQVjrmuwdWgnKKGn
|
15
|
+
9XMEp8lBC7KwAnaQKP7c83WarU/FbF08BbPkHob1wuAyMrD7wRv2XDcCgYEAxnl8
|
16
|
+
SuHwIooIyiW2/oDjoqCrdtgOLXzdOK2OSDcY+jARVkOA8N0ingPOb18RLOTmjGk5
|
17
|
+
KZDHa8xZzdd0Bt7xz3WV2FipYxnkkY7sJosJpMrY8k/QUip9i2D04uLypwVBfT7P
|
18
|
+
q3GOgOrP+nvRya8HLHKm0rf7+sU2mGIsSrVYtI0CgYBzQUIoL5FPW0e4XQFG/FJx
|
19
|
+
29NcBQk1DMsq8CB2KnZSvhS35st3O+rDIE4/vKrLDVRmS9UkuUcJ+VaKHler0s2A
|
20
|
+
a8iKT7GoHt2YNZKFSEzVKJ1R6cVLXvUJZihvsSivGBd6cLuzplWgwEQS2gBBsWJ6
|
21
|
+
w1CLzpYwHyU1jtIUmtAV7QKBgCtC3bnAx8PvjHzrfZi55WRUWyt7apO1rM6m3eWV
|
22
|
+
xOb7xTulWRynRt1kfQG/mhHMDwi6AtCxkxZHI6f/d3Xr8I9E1RWkNb+5LB4iJg08
|
23
|
+
ryxxXppqlUDjrBvOVXKC/1syhRTUtRVsmiA1joHNrWulsA2bLAuwOMdvZzgN5hOe
|
24
|
+
tagdAoGAP7kdbprmkT/7xX8puX6WD4MXQ+dgyb3FvpCIfQT8x0t/ndMI2wMc4keg
|
25
|
+
woD2L56tjtVyFH8LQz1sU7LroSc8XF2joZOdQePrnyTVUISoMiTqaXMPIO6l6pez
|
26
|
+
x7g1PP3ey5LOoX7LG5ule/6qNMtRhVOFok0vA9ZuuIIkkmYSo1c=
|
27
|
+
-----END RSA PRIVATE KEY-----
|