saml_idp 0.7.2 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Gemfile +1 -1
- data/README.md +59 -52
- data/lib/saml_idp/assertion_builder.rb +28 -3
- data/lib/saml_idp/configurator.rb +7 -1
- data/lib/saml_idp/controller.rb +21 -13
- data/lib/saml_idp/encryptor.rb +0 -1
- data/lib/saml_idp/fingerprint.rb +19 -0
- data/lib/saml_idp/incoming_metadata.rb +22 -1
- data/lib/saml_idp/metadata_builder.rb +23 -8
- data/lib/saml_idp/persisted_metadata.rb +4 -0
- data/lib/saml_idp/request.rb +26 -6
- data/lib/saml_idp/response_builder.rb +26 -6
- data/lib/saml_idp/saml_response.rb +62 -28
- data/lib/saml_idp/service_provider.rb +15 -6
- data/lib/saml_idp/signable.rb +1 -2
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp/xml_security.rb +1 -1
- data/lib/saml_idp.rb +2 -1
- data/saml_idp.gemspec +45 -42
- data/spec/acceptance/idp_controller_spec.rb +5 -4
- data/spec/lib/saml_idp/algorithmable_spec.rb +6 -6
- data/spec/lib/saml_idp/assertion_builder_spec.rb +151 -8
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
- data/spec/lib/saml_idp/configurator_spec.rb +9 -7
- data/spec/lib/saml_idp/controller_spec.rb +53 -20
- data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
- data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
- data/spec/lib/saml_idp/incoming_metadata_spec.rb +60 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +30 -17
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +3 -3
- data/spec/lib/saml_idp/request_spec.rb +78 -27
- data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
- data/spec/lib/saml_idp/saml_response_spec.rb +127 -12
- data/spec/lib/saml_idp/service_provider_spec.rb +2 -2
- data/spec/lib/saml_idp/signable_spec.rb +1 -1
- data/spec/lib/saml_idp/signature_builder_spec.rb +2 -2
- data/spec/lib/saml_idp/signed_info_builder_spec.rb +3 -3
- data/spec/rails_app/app/controllers/saml_controller.rb +1 -1
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
- data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +1 -5
- data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
- data/spec/rails_app/config/application.rb +1 -6
- data/spec/rails_app/config/boot.rb +1 -1
- data/spec/rails_app/config/environments/development.rb +2 -5
- data/spec/rails_app/config/environments/production.rb +1 -0
- data/spec/rails_app/config/environments/test.rb +1 -0
- data/spec/spec_helper.rb +23 -1
- data/spec/support/certificates/sp_cert_req.csr +12 -0
- data/spec/support/certificates/sp_private_key.pem +16 -0
- data/spec/support/certificates/sp_x509_cert.crt +18 -0
- data/spec/support/saml_request_macros.rb +66 -4
- data/spec/support/security_helpers.rb +10 -0
- data/spec/xml_security_spec.rb +12 -12
- metadata +135 -81
- data/app/controllers/saml_idp/idp_controller.rb +0 -59
- data/spec/lib/saml_idp/.assertion_builder_spec.rb.swp +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7cd98d292836070eddac0b7f710dbd15b0d3611cc849de827b999c3a2fa12d86
|
4
|
+
data.tar.gz: f49f1035b63375d5b8aebfd38484c1859d8ac35520f82df92fc698cea00dde1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ab18f32c643b5c8be43a093349ff5c1c0f3142b8156fb650f749ffde2ce1306e104d4006178f77f27055bc6683a35ffbf8aa1d79331140b9c94ae7a2a1da7ee
|
7
|
+
data.tar.gz: e68e9a8f0dd08f5dd9aa31c8ff8461ca229b97cbc5e9813eed9c3575ee65bafd2c766bcfd19d9081d0956bab62b8819574b8765be692156d10967589a791cda3
|
data/Gemfile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
source "
|
1
|
+
source "https://rubygems.org"
|
2
2
|
gemspec
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Ruby SAML Identity Provider (IdP)
|
2
|
-
Forked from https://github.com/lawrencepit/ruby-saml-idp
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
Forked from <https://github.com/lawrencepit/ruby-saml-idp>
|
4
|
+
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/saml_idp.svg)](http://badge.fury.io/rb/saml_idp)
|
6
6
|
|
7
7
|
The ruby SAML Identity Provider library is for implementing the server side of SAML authentication. It allows
|
8
8
|
your application to act as an IdP (Identity Provider) using the
|
@@ -12,62 +12,49 @@ protocol. It provides a means for managing authentication requests and confirmat
|
|
12
12
|
This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real
|
13
13
|
SAML IDP implementation.
|
14
14
|
|
15
|
-
|
15
|
+
## Installation and Usage
|
16
16
|
|
17
17
|
Add this to your Gemfile:
|
18
18
|
|
19
|
+
```ruby
|
19
20
|
gem 'saml_idp'
|
21
|
+
```
|
22
|
+
|
23
|
+
### Not using rails?
|
20
24
|
|
21
|
-
## Not using rails?
|
22
25
|
Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
|
23
26
|
|
24
27
|
Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
|
25
28
|
`saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
|
26
29
|
a user is entirely up to you.
|
27
30
|
|
28
|
-
Once a user has successfully authenticated on your system send the Service Provider a
|
31
|
+
Once a user has successfully authenticated on your system send the Service Provider a SAMLResponse by
|
29
32
|
posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
|
30
33
|
`encode_response(user_email)`.
|
31
34
|
|
32
|
-
|
33
|
-
Add to your `routes.rb` file, for example:
|
35
|
+
### Using rails?
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
get '/saml/metadata' => 'saml_idp#show'
|
38
|
-
post '/saml/auth' => 'saml_idp#create'
|
39
|
-
match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
|
40
|
-
```
|
37
|
+
Check out our Wiki page for Rails integration
|
38
|
+
[Rails Integration guide](https://github.com/saml-idp/saml_idp/wiki/Rails_Integration)
|
41
39
|
|
42
|
-
|
40
|
+
### Configuration
|
43
41
|
|
44
|
-
|
45
|
-
class SamlIdpController < SamlIdp::IdpController
|
46
|
-
def idp_authenticate(email, password) # not using params intentionally
|
47
|
-
user = User.by_email(email).first
|
48
|
-
user && user.valid_password?(password) ? user : nil
|
49
|
-
end
|
50
|
-
private :idp_authenticate
|
51
|
-
|
52
|
-
def idp_make_saml_response(found_user) # not using params intentionally
|
53
|
-
# NOTE encryption is optional
|
54
|
-
encode_response found_user, encryption: {
|
55
|
-
cert: saml_request.service_provider.cert,
|
56
|
-
block_encryption: 'aes256-cbc',
|
57
|
-
key_transport: 'rsa-oaep-mgf1p'
|
58
|
-
}
|
59
|
-
end
|
60
|
-
private :idp_make_saml_response
|
42
|
+
#### Signed assertions and Signed Response
|
61
43
|
|
62
|
-
|
63
|
-
|
64
|
-
user.logout
|
65
|
-
end
|
66
|
-
private :idp_logout
|
67
|
-
end
|
68
|
-
```
|
44
|
+
By default SAML Assertion will be signed with an algorithm which defined to `config.algorithm`, because SAML assertions contain secure information used for authentication such as NameID.
|
45
|
+
Besides that, signing assertions could be optional and can be defined with `config.signed_assertion` option. Setting this configuration flag to `false` will add raw assertions on the response instead of signed ones. If the response is encrypted the `config.signed_assertion` will be ignored and all assertions will be signed.
|
69
46
|
|
70
|
-
|
47
|
+
Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
|
48
|
+
For that, you can enable it with `signed_message: true` option for `encode_response(user_email, signed_message: true)` method. [More about SAML spec](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=68)
|
49
|
+
|
50
|
+
#### Signing algorithm
|
51
|
+
|
52
|
+
Following algorithms you can set in your response signing algorithm
|
53
|
+
:sha1 - RSA-SHA1 default value but not recommended to production environment
|
54
|
+
Highly recommended to use one of following algorithm, suit with your computing power.
|
55
|
+
:sha256 - RSA-SHA256
|
56
|
+
:sha384 - RSA-SHA384
|
57
|
+
:sha512 - RSA-SHA512
|
71
58
|
|
72
59
|
Be sure to load a file like this during your app initialization:
|
73
60
|
|
@@ -88,18 +75,23 @@ KEY DATA
|
|
88
75
|
CERT
|
89
76
|
|
90
77
|
# config.password = "secret_key_password"
|
91
|
-
# config.algorithm = :sha256
|
78
|
+
# config.algorithm = :sha256 # Default: sha1 only for development.
|
92
79
|
# config.organization_name = "Your Organization"
|
93
80
|
# config.organization_url = "http://example.com"
|
94
81
|
# config.base_saml_location = "#{base}/saml"
|
95
|
-
# config.reference_id_generator # Default: -> {
|
82
|
+
# config.reference_id_generator # Default: -> { SecureRandom.uuid }
|
83
|
+
# config.single_logout_service_post_location = "#{base}/saml/logout"
|
84
|
+
# config.single_logout_service_redirect_location = "#{base}/saml/logout"
|
96
85
|
# config.attribute_service_location = "#{base}/saml/attributes"
|
97
86
|
# config.single_service_post_location = "#{base}/saml/auth"
|
98
87
|
# config.session_expiry = 86400 # Default: 0 which means never
|
88
|
+
# config.signed_assertion = false # Default: true which means signed assertions on the SAML Response
|
89
|
+
# config.compress = true # Default: false which means the SAML Response is not being compressed
|
90
|
+
# config.logger = ::Logger.new($stdout) # Default: if in Rails context - Rails.logger, else ->(msg) { puts msg }. Works with either a Ruby Logger or a lambda
|
99
91
|
|
100
92
|
# Principal (e.g. User) is passed in when you `encode_response`
|
101
93
|
#
|
102
|
-
# config.name_id.formats
|
94
|
+
# config.name_id.formats =
|
103
95
|
# { # All 2.0
|
104
96
|
# email_address: -> (principal) { principal.email_address },
|
105
97
|
# transient: -> (principal) { principal.id },
|
@@ -169,7 +161,11 @@ CERT
|
|
169
161
|
service_providers = {
|
170
162
|
"some-issuer-url.com/saml" => {
|
171
163
|
fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D",
|
172
|
-
metadata_url: "http://some-issuer-url.com/saml/metadata"
|
164
|
+
metadata_url: "http://some-issuer-url.com/saml/metadata",
|
165
|
+
|
166
|
+
# We now validate AssertionConsumerServiceURL will match the MetadataURL set above.
|
167
|
+
# *If* it's not going to match your Metadata URL's Host, then set this so we can validate the host using this list
|
168
|
+
response_hosts: ["foo.some-issuer-url.com"]
|
173
169
|
},
|
174
170
|
}
|
175
171
|
|
@@ -177,7 +173,7 @@ CERT
|
|
177
173
|
# settings is an IncomingMetadata object which has a to_h method that needs to be persisted
|
178
174
|
config.service_provider.metadata_persister = ->(identifier, settings) {
|
179
175
|
fname = identifier.to_s.gsub(/\/|:/,"_")
|
180
|
-
|
176
|
+
FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
|
181
177
|
File.open Rails.root.join("cache/saml/metadata/#{fname}"), "r+b" do |f|
|
182
178
|
Marshal.dump settings.to_h, f
|
183
179
|
end
|
@@ -188,7 +184,7 @@ CERT
|
|
188
184
|
# `service_provider` you should return the settings.to_h from above
|
189
185
|
config.service_provider.persisted_metadata_getter = ->(identifier, service_provider){
|
190
186
|
fname = identifier.to_s.gsub(/\/|:/,"_")
|
191
|
-
|
187
|
+
FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
|
192
188
|
full_filename = Rails.root.join("cache/saml/metadata/#{fname}")
|
193
189
|
if File.file?(full_filename)
|
194
190
|
File.open full_filename, "rb" do |f|
|
@@ -204,7 +200,8 @@ CERT
|
|
204
200
|
end
|
205
201
|
```
|
206
202
|
|
207
|
-
|
203
|
+
## Keys and Secrets
|
204
|
+
|
208
205
|
To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
|
209
206
|
You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
|
210
207
|
Obviously you shouldn't use these if you intend to use this in production environments. In that case,
|
@@ -214,22 +211,32 @@ and `SamlIdp.config.secret_key` properties.
|
|
214
211
|
|
215
212
|
The fingerprint to use, if you use the default X.509 certificate of this gem, is:
|
216
213
|
|
214
|
+
```bash
|
215
|
+
9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
|
217
216
|
```
|
218
|
-
|
217
|
+
|
218
|
+
## Fingerprint
|
219
|
+
|
220
|
+
The gem provides an helper to generate a fingerprint for a X.509 certificate.
|
221
|
+
The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
SamlIdp::Fingerprint.certificate_digest(x509_cert, :sha512)
|
219
225
|
```
|
220
226
|
|
227
|
+
## Service Providers
|
221
228
|
|
222
|
-
# Service Providers
|
223
229
|
To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
|
224
230
|
excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
|
225
231
|
|
232
|
+
## Author
|
226
233
|
|
227
|
-
|
228
|
-
Jon Phenow, me@jphenow.com
|
234
|
+
Jon Phenow, jon@jphenow.com, jphenow.com, @jphenow
|
229
235
|
|
230
236
|
Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
|
231
237
|
|
232
|
-
|
238
|
+
## Copyright
|
239
|
+
|
233
240
|
Copyright (c) 2012 Sport Ngin.
|
234
241
|
Portions Copyright (c) 2010 OneLogin, LLC
|
235
242
|
Portions Copyright (c) 2012 Lawrence Pit (http://lawrencepit.com)
|
@@ -16,10 +16,26 @@ module SamlIdp
|
|
16
16
|
attr_accessor :expiry
|
17
17
|
attr_accessor :encryption_opts
|
18
18
|
attr_accessor :session_expiry
|
19
|
+
attr_accessor :name_id_formats_opts
|
20
|
+
attr_accessor :asserted_attributes_opts
|
19
21
|
|
20
22
|
delegate :config, to: :SamlIdp
|
21
23
|
|
22
|
-
def initialize(
|
24
|
+
def initialize(
|
25
|
+
reference_id,
|
26
|
+
issuer_uri,
|
27
|
+
principal,
|
28
|
+
audience_uri,
|
29
|
+
saml_request_id,
|
30
|
+
saml_acs_url,
|
31
|
+
raw_algorithm,
|
32
|
+
authn_context_classref,
|
33
|
+
expiry=60*60,
|
34
|
+
encryption_opts=nil,
|
35
|
+
session_expiry=nil,
|
36
|
+
name_id_formats_opts = nil,
|
37
|
+
asserted_attributes_opts = nil
|
38
|
+
)
|
23
39
|
self.reference_id = reference_id
|
24
40
|
self.issuer_uri = issuer_uri
|
25
41
|
self.principal = principal
|
@@ -31,6 +47,8 @@ module SamlIdp
|
|
31
47
|
self.expiry = expiry
|
32
48
|
self.encryption_opts = encryption_opts
|
33
49
|
self.session_expiry = session_expiry.nil? ? config.session_expiry : session_expiry
|
50
|
+
self.name_id_formats_opts = name_id_formats_opts
|
51
|
+
self.asserted_attributes_opts = asserted_attributes_opts
|
34
52
|
end
|
35
53
|
|
36
54
|
def fresh
|
@@ -98,7 +116,9 @@ module SamlIdp
|
|
98
116
|
end
|
99
117
|
|
100
118
|
def asserted_attributes
|
101
|
-
if
|
119
|
+
if asserted_attributes_opts.present? && !asserted_attributes_opts.empty?
|
120
|
+
asserted_attributes_opts
|
121
|
+
elsif principal.respond_to?(:asserted_attributes)
|
102
122
|
principal.send(:asserted_attributes)
|
103
123
|
elsif !config.attributes.nil? && !config.attributes.empty?
|
104
124
|
config.attributes
|
@@ -139,10 +159,15 @@ module SamlIdp
|
|
139
159
|
private :name_id_getter
|
140
160
|
|
141
161
|
def name_id_format
|
142
|
-
@name_id_format ||= NameIdFormatter.new(
|
162
|
+
@name_id_format ||= NameIdFormatter.new(name_id_formats).chosen
|
143
163
|
end
|
144
164
|
private :name_id_format
|
145
165
|
|
166
|
+
def name_id_formats
|
167
|
+
@name_id_formats ||= (name_id_formats_opts || config.name_id.formats)
|
168
|
+
end
|
169
|
+
private :name_id_formats
|
170
|
+
|
146
171
|
def reference_string
|
147
172
|
"_#{reference_id}"
|
148
173
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'ostruct'
|
3
|
+
require 'securerandom'
|
4
|
+
|
3
5
|
module SamlIdp
|
4
6
|
class Configurator
|
5
7
|
attr_accessor :x509_certificate
|
@@ -13,23 +15,27 @@ module SamlIdp
|
|
13
15
|
attr_accessor :reference_id_generator
|
14
16
|
attr_accessor :attribute_service_location
|
15
17
|
attr_accessor :single_service_post_location
|
18
|
+
attr_accessor :single_service_redirect_location
|
16
19
|
attr_accessor :single_logout_service_post_location
|
17
20
|
attr_accessor :single_logout_service_redirect_location
|
18
21
|
attr_accessor :attributes
|
19
22
|
attr_accessor :service_provider
|
23
|
+
attr_accessor :assertion_consumer_service_hosts
|
20
24
|
attr_accessor :session_expiry
|
25
|
+
attr_accessor :logger
|
21
26
|
|
22
27
|
def initialize
|
23
28
|
self.x509_certificate = Default::X509_CERTIFICATE
|
24
29
|
self.secret_key = Default::SECRET_KEY
|
25
30
|
self.algorithm = :sha1
|
26
|
-
self.reference_id_generator = ->() {
|
31
|
+
self.reference_id_generator = ->() { SecureRandom.uuid }
|
27
32
|
self.service_provider = OpenStruct.new
|
28
33
|
self.service_provider.finder = ->(_) { Default::SERVICE_PROVIDER }
|
29
34
|
self.service_provider.metadata_persister = ->(id, settings) { }
|
30
35
|
self.service_provider.persisted_metadata_getter = ->(id, service_provider) { }
|
31
36
|
self.session_expiry = 0
|
32
37
|
self.attributes = {}
|
38
|
+
self.logger = defined?(::Rails) ? Rails.logger : ->(msg) { puts msg }
|
33
39
|
end
|
34
40
|
|
35
41
|
# formats
|
data/lib/saml_idp/controller.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
require 'openssl'
|
3
2
|
require 'base64'
|
4
3
|
require 'time'
|
5
|
-
require '
|
4
|
+
require 'securerandom'
|
6
5
|
require 'saml_idp/request'
|
7
6
|
require 'saml_idp/logout_response_builder'
|
8
7
|
module SamlIdp
|
@@ -35,13 +34,10 @@ module SamlIdp
|
|
35
34
|
|
36
35
|
def validate_saml_request(raw_saml_request = params[:SAMLRequest])
|
37
36
|
decode_request(raw_saml_request)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
render nothing: true, status: :forbidden
|
43
|
-
end
|
44
|
-
end
|
37
|
+
return true if valid_saml_request?
|
38
|
+
|
39
|
+
head :forbidden if defined?(::Rails)
|
40
|
+
false
|
45
41
|
end
|
46
42
|
|
47
43
|
def decode_request(raw_saml_request)
|
@@ -62,6 +58,13 @@ module SamlIdp
|
|
62
58
|
expiry = opts[:expiry] || 60*60
|
63
59
|
session_expiry = opts[:session_expiry]
|
64
60
|
encryption_opts = opts[:encryption] || nil
|
61
|
+
name_id_formats_opts = opts[:name_id_formats] || nil
|
62
|
+
asserted_attributes_opts = opts[:attributes] || nil
|
63
|
+
signed_message_opts = opts[:signed_message] || false
|
64
|
+
name_id_formats_opts = opts[:name_id_formats] || nil
|
65
|
+
asserted_attributes_opts = opts[:attributes] || nil
|
66
|
+
signed_assertion_opts = opts[:signed_assertion] || true
|
67
|
+
compress_opts = opts[:compress] || false
|
65
68
|
|
66
69
|
SamlResponse.new(
|
67
70
|
reference_id,
|
@@ -75,11 +78,16 @@ module SamlIdp
|
|
75
78
|
my_authn_context_classref,
|
76
79
|
expiry,
|
77
80
|
encryption_opts,
|
78
|
-
session_expiry
|
81
|
+
session_expiry,
|
82
|
+
name_id_formats_opts,
|
83
|
+
asserted_attributes_opts,
|
84
|
+
signed_message_opts,
|
85
|
+
signed_assertion_opts,
|
86
|
+
compress_opts
|
79
87
|
).build
|
80
88
|
end
|
81
89
|
|
82
|
-
def encode_logout_response(
|
90
|
+
def encode_logout_response(_principal, opts = {})
|
83
91
|
SamlIdp::LogoutResponseBuilder.new(
|
84
92
|
get_saml_response_id,
|
85
93
|
(opts[:issuer_uri] || issuer_uri),
|
@@ -122,11 +130,11 @@ module SamlIdp
|
|
122
130
|
end
|
123
131
|
|
124
132
|
def get_saml_response_id
|
125
|
-
|
133
|
+
SecureRandom.uuid
|
126
134
|
end
|
127
135
|
|
128
136
|
def get_saml_reference_id
|
129
|
-
|
137
|
+
SecureRandom.uuid
|
130
138
|
end
|
131
139
|
|
132
140
|
def default_algorithm
|
data/lib/saml_idp/encryptor.rb
CHANGED
@@ -61,7 +61,6 @@ module SamlIdp
|
|
61
61
|
key_info.EncryptedKey Id: 'EK', xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_key|
|
62
62
|
enc_key.EncryptionMethod Algorithm: key_transport_ns
|
63
63
|
enc_key.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info2|
|
64
|
-
key_info2.tag! 'ds:KeyName'
|
65
64
|
key_info2.tag! 'ds:X509Data' do |x509_data|
|
66
65
|
x509_data.tag! 'ds:X509Certificate' do |x509_cert|
|
67
66
|
x509_cert << cert.to_s.gsub(/-+(BEGIN|END) CERTIFICATE-+/, '')
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SamlIdp
|
2
|
+
module Fingerprint
|
3
|
+
def self.certificate_digest(cert, sha_size = nil)
|
4
|
+
sha_size ||= SamlIdp.config.algorithm
|
5
|
+
digest_sha_class(sha_size).hexdigest(OpenSSL::X509::Certificate.new(cert).to_der).scan(/../).join(':')
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.digest_sha_class(sha_size)
|
9
|
+
case sha_size
|
10
|
+
when :sha256
|
11
|
+
Digest::SHA256
|
12
|
+
when :sha512
|
13
|
+
Digest::SHA512
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Unsupported sha size parameter: #{sha_size}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -16,16 +16,37 @@ module SamlIdp
|
|
16
16
|
@document ||= Saml::XML::Document.parse raw
|
17
17
|
end
|
18
18
|
|
19
|
+
def entity_id
|
20
|
+
xpath('//md:EntityDescriptor/@entityID', md: metadata_namespace).first.try(:content).to_s
|
21
|
+
end
|
22
|
+
hashable :entity_id
|
23
|
+
|
19
24
|
def sign_assertions
|
20
25
|
doc = xpath(
|
21
26
|
"//md:SPSSODescriptor",
|
22
27
|
ds: signature_namespace,
|
23
28
|
md: metadata_namespace
|
24
29
|
).first
|
25
|
-
doc
|
30
|
+
if (doc && !doc['WantAssertionsSigned'].nil?)
|
31
|
+
return doc['WantAssertionsSigned'].strip.downcase == 'true'
|
32
|
+
end
|
33
|
+
return false
|
26
34
|
end
|
27
35
|
hashable :sign_assertions
|
28
36
|
|
37
|
+
def sign_authn_request
|
38
|
+
doc = xpath(
|
39
|
+
"//md:SPSSODescriptor",
|
40
|
+
ds: signature_namespace,
|
41
|
+
md: metadata_namespace
|
42
|
+
).first
|
43
|
+
if (doc && !doc['AuthnRequestsSigned'].nil?)
|
44
|
+
return doc['AuthnRequestsSigned'].strip.downcase == 'true'
|
45
|
+
end
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
hashable :sign_authn_request
|
49
|
+
|
29
50
|
def display_name
|
30
51
|
role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
|
31
52
|
end
|
@@ -24,13 +24,15 @@ module SamlIdp
|
|
24
24
|
|
25
25
|
entity.IDPSSODescriptor protocolSupportEnumeration: protocol_enumeration do |descriptor|
|
26
26
|
build_key_descriptor descriptor
|
27
|
-
descriptor
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
build_endpoint descriptor, [
|
28
|
+
{ tag: 'SingleLogoutService', url: single_logout_service_post_location, bind: 'HTTP-POST' },
|
29
|
+
{ tag: 'SingleLogoutService', url: single_logout_service_redirect_location, bind: 'HTTP-Redirect'}
|
30
|
+
]
|
31
31
|
build_name_id_formats descriptor
|
32
|
-
descriptor
|
33
|
-
|
32
|
+
build_endpoint descriptor, [
|
33
|
+
{ tag: 'SingleSignOnService', url: single_service_post_location, bind: 'HTTP-POST' },
|
34
|
+
{ tag: 'SingleSignOnService', url: single_service_redirect_location, bind: 'HTTP-Redirect'}
|
35
|
+
]
|
34
36
|
build_attribute descriptor
|
35
37
|
end
|
36
38
|
|
@@ -38,8 +40,9 @@ module SamlIdp
|
|
38
40
|
build_key_descriptor authority_descriptor
|
39
41
|
build_organization authority_descriptor
|
40
42
|
build_contact authority_descriptor
|
41
|
-
authority_descriptor
|
42
|
-
|
43
|
+
build_endpoint authority_descriptor, [
|
44
|
+
{ tag: 'AttributeService', url: attribute_service_location, bind: 'HTTP-Redirect' }
|
45
|
+
]
|
43
46
|
build_name_id_formats authority_descriptor
|
44
47
|
build_attribute authority_descriptor
|
45
48
|
end
|
@@ -69,6 +72,17 @@ module SamlIdp
|
|
69
72
|
end
|
70
73
|
private :build_name_id_formats
|
71
74
|
|
75
|
+
def build_endpoint(el, end_points)
|
76
|
+
end_points.each do |ep|
|
77
|
+
next unless ep[:url].present?
|
78
|
+
|
79
|
+
el.tag! ep[:tag],
|
80
|
+
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:#{ep[:bind]}",
|
81
|
+
Location: ep[:url]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
private :build_endpoint
|
85
|
+
|
72
86
|
def build_attribute(el)
|
73
87
|
attributes.each do |attribute|
|
74
88
|
el.tag! "saml:Attribute",
|
@@ -151,6 +165,7 @@ module SamlIdp
|
|
151
165
|
organization_url
|
152
166
|
attribute_service_location
|
153
167
|
single_service_post_location
|
168
|
+
single_service_redirect_location
|
154
169
|
single_logout_service_post_location
|
155
170
|
single_logout_service_redirect_location
|
156
171
|
technical_contact
|
data/lib/saml_idp/request.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'saml_idp/xml_security'
|
2
2
|
require 'saml_idp/service_provider'
|
3
|
+
require 'logger'
|
3
4
|
module SamlIdp
|
4
5
|
class Request
|
5
6
|
def self.from_deflated_request(raw)
|
@@ -77,10 +78,10 @@ module SamlIdp
|
|
77
78
|
end
|
78
79
|
|
79
80
|
def log(msg)
|
80
|
-
if
|
81
|
-
|
81
|
+
if config.logger.respond_to?(:call)
|
82
|
+
config.logger.call msg
|
82
83
|
else
|
83
|
-
|
84
|
+
config.logger.info msg
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
@@ -105,13 +106,24 @@ module SamlIdp
|
|
105
106
|
return false
|
106
107
|
end
|
107
108
|
|
109
|
+
if !service_provider.acceptable_response_hosts.include?(response_host)
|
110
|
+
log "#{service_provider.acceptable_response_hosts} compare to #{response_host}"
|
111
|
+
log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host"
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
|
108
115
|
return true
|
109
116
|
end
|
110
117
|
|
111
118
|
def valid_signature?
|
112
|
-
# Force signatures for logout requests because there is no other
|
113
|
-
#
|
114
|
-
service_provider.
|
119
|
+
# Force signatures for logout requests because there is no other protection against a cross-site DoS.
|
120
|
+
# Validate signature when metadata specify AuthnRequest should be signed
|
121
|
+
metadata = service_provider.current_metadata
|
122
|
+
if logout_request? || authn_request? && metadata.respond_to?(:sign_authn_request?) && metadata.sign_authn_request?
|
123
|
+
document.valid_signature?(service_provider.fingerprint)
|
124
|
+
else
|
125
|
+
true
|
126
|
+
end
|
115
127
|
end
|
116
128
|
|
117
129
|
def service_provider?
|
@@ -136,6 +148,14 @@ module SamlIdp
|
|
136
148
|
@_session_index ||= xpath("//samlp:SessionIndex", samlp: samlp).first.try(:content)
|
137
149
|
end
|
138
150
|
|
151
|
+
def response_host
|
152
|
+
uri = URI(response_url)
|
153
|
+
if uri
|
154
|
+
uri.host
|
155
|
+
end
|
156
|
+
end
|
157
|
+
private :response_host
|
158
|
+
|
139
159
|
def document
|
140
160
|
@_document ||= Saml::XML::Document.parse(raw_xml)
|
141
161
|
end
|